Correct a quote or an invoice
maintenance.invoice.update
POST/api/v1/households/{household_id}/commands/maintenance.invoice.update
- Command
- Rigel runs it
- Applies offline
Sets only the fields given; null clears an optional one. When lines is given it replaces all the old lines, so send the whole list. Everything linked must be at the invoice's property (different_property).
Parameters#
| Field | Description |
|---|---|
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.
| Field | Description |
|---|---|
invoice_idstringrequired | A invoice ID, such as ivc_01JC0000000000000000000000. |
kindone of: quote, invoiceoptional | quote (an estimate of work) or invoice (a bill for work done). |
file_idstring or nulloptional | The original paper; null detaches it. |
contractor_idstring or nulloptional | Who issued it; null clears it. |
visit_idstring or nulloptional | The visit it is for; null clears it. |
project_idstring or nulloptional | The project it is for; null clears it. |
numberstring or nulloptional | No description. |
issued_onstring (date) or nulloptional | A calendar date, YYYY-MM-DD. |
service_onstring (date) or nulloptional | A calendar date, YYYY-MM-DD. |
currencystringoptional | An ISO 4217 currency code, such as USD. |
subtotalinteger or nulloptional | The amount before tax, in minor units. |
taxinteger or nulloptional | The tax, in minor units. |
totalinteger or nulloptional | The total as the paper gives it, in minor units. Recording it doesn't mean it was paid. |
notesstring or nulloptional | Anything else worth keeping. |
linesarray of objectsoptional | The paper's line items, in order. Replaces the existing lines. |
lines[].descriptionstringrequired | What the line is for, as the paper words it. |
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. |
lines[].unit_priceintegeroptional | The price of one, in minor units. |
lines[].amountintegeroptional | The line's total in minor units; negative for a discount. |
lines[].asset_idstringoptional | The item this line is for. |
Response#
200 The command was applied.
| Field | Description |
|---|---|
resultobjectrequired | What the command returned, such as the IDs it created. |
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.
| Status | Code | When |
|---|---|---|
400 | validation_failed | A field is missing, misspelled or out of range. details.fields lists each problem. |
401 | unauthenticated | The request has no valid session. |
404 | file_not_found | A file that isn't there. |
404 | invoice_not_found | An invoice that isn't there. |
404 | not_found | The household doesn't exist, or you aren't a member of it. |
409 | different_property |
|
409 | idempotency_key_reused | The 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.
Changes only what's given and leaves the lines
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"total": 200,
"number": null,
"kind": "quote"
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
total: 200,
number: null,
kind: "quote",
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"total": 200,
"number": null,
"kind": "quote"
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R07VNMM80NJWV1GA8W
{
"result": {},
"version": 1
}Lines given replace the old ones
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": [
{
"description": "Everything",
"amount": 500,
"asset_id": "ast_01JC00000000000000000000A1"
},
{
"description": "Discount",
"kind": "discount",
"amount": -50
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
lines: [
{
description: "Everything",
amount: 500,
asset_id: "ast_01JC00000000000000000000A1",
},
{
description: "Discount",
kind: "discount",
amount: -50,
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": [
{
"description": "Everything",
"amount": 500,
"asset_id": "ast_01JC00000000000000000000A1"
},
{
"description": "Discount",
"kind": "discount",
"amount": -50
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0SNH937RK01ME08M0
{
"result": {},
"version": 1
}An empty list removes every line
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": []
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
lines: [],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": []
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0X2Z0M7KY7ETGNC90
{
"result": {},
"version": 1
}Links can be set and cleared
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"contractor_id": "ctr_01JC00000000000000000000K1",
"visit_id": "vst_01JC00000000000000000000V1",
"project_id": null,
"file_id": null
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
contractor_id: "ctr_01JC00000000000000000000K1",
visit_id: "vst_01JC00000000000000000000V1",
project_id: null,
file_id: null,
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"contractor_id": "ctr_01JC00000000000000000000K1",
"visit_id": "vst_01JC00000000000000000000V1",
"project_id": null,
"file_id": null
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R012D09AWS3WCFARBD
{
"result": {},
"version": 1
}A visit at another property
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"visit_id": "vst_01JC00000000000000000000V1"
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
visit_id: "vst_01JC00000000000000000000V1",
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"visit_id": "vst_01JC00000000000000000000V1"
}HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0N4ADM96K3EMT4KYV
{
"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_01K9D9S2R0N4ADM96K3EMT4KYV"
}
}A line's item at another property
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": [
{
"description": "x",
"asset_id": "ast_01JC00000000000000000000A2"
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
lines: [
{
description: "x",
asset_id: "ast_01JC00000000000000000000A2",
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"lines": [
{
"description": "x",
"asset_id": "ast_01JC00000000000000000000A2"
}
]
}HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0DSQJAV47RESBGP7M
{
"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_01K9D9S2R0DSQJAV47RESBGP7M"
}
}A file that isn't there
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"file_id": "fil_01JC00000000000000000000F1"
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
file_id: "fil_01JC00000000000000000000F1",
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"file_id": "fil_01JC00000000000000000000F1"
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R061X6AJMTSKYF5QQ5
{
"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_01K9D9S2R061X6AJMTSKYF5QQ5"
}
}An invoice that isn't there
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"invoice_id": "ivc_01JC00000000000000000000B1",
"total": 1
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
invoice_id: "ivc_01JC00000000000000000000B1",
total: 1,
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.update HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"invoice_id": "ivc_01JC00000000000000000000B1",
"total": 1
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0SPJJ38T35RCSQTD0
{
"error": {
"code": "invoice_not_found",
"message": "That quote or invoice isn't there any more. No changes were applied.",
"details": {
"invoice_id": "ivc_01JC00000000000000000000B1"
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0SPJJ38T35RCSQTD0"
}
}For Rigel#
Rigel's tool for this command is maintenance_invoice_update. Rigel runs this on its own, then says what it did. The agent guide has the rest.