Skip to content

Depot psa

Registers the whole client-billing surface.

This page is generated from the Depot route table. Every operation carries the permission it needs and the anchor an agent can link to.

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List agreements

NameInTypeRequiredDescription
cidpathstringYes
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultAgreementSummaryViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of AgreementSummaryView
items[].billingDayrequiredinteger
items[].defaultChargeTypeCodestring or null
items[].defaultRateCentsrequiredinteger

int64.

items[].endsOnstring or null
items[].namerequiredstring
items[].prepaidBalanceMinutesinteger or null
items[].publicIdrequiredstring
items[].recurringCentsrequiredinteger

int64.

items[].startsOnrequiredstring
items[].statusrequiredstring
items[].templatePublicIdstring or null
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POSTpsa:writemsp facemsp

Source: [object Object]

Create agreement

NameInTypeRequiredDescription
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
billingDayinteger or null
defaultChargeTypeCoderequiredany

Arbitrary JSON.

defaultRateCentsinteger or null

int64.

endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namestring or null
notesstring or null
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnstring or null
templateIdstring or null
StatusBodyDescription
201application/json, AgreementViewCreated.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/agreements' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"billingDay": 0,
"defaultChargeTypeCode": "string",
"defaultRateCents": 0,
"endsOn": "string",
"lines": [
{
"active": false,
"balanceMinutes": 0,
"description": "string",
"exclusive": false,
"filter": "string",
"includedDevicesPerSeat": 0,
"includedMinutes": 0,
"kind": "string",
"overageUnitAmountCents": 0,
"publicId": "string",
"seatRequiresEndpoint": false,
"sortOrder": 0,
"unitAmountCents": 0,
"usageMethod": "string"
}
],
"name": "string",
"notes": "string",
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
],
"startsOn": "string",
"templateId": "string"
}'

GET /api/v1/clients/{cid}/agreements/{agreementID}

Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

Get agreement

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
200application/json, AgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PATCH /api/v1/clients/{cid}/agreements/{agreementID}

Section titled “PATCH /api/v1/clients/{cid}/agreements/{agreementID}”

PATCHpsa:writemsp facemsp

Source: [object Object]

patchAgreement merges the body onto the stored agreement.

RULES ARE NOT TOUCHED HERE even when the body carries them. Replacing the ordered array is a different operation with a different consequence, it reprices unbilled work, and it has its own route so that consequence is something an operator asked for rather than a side effect of renaming a contract.

LINES MERGE BY publicId AND ARE NEVER DESTROYED. A line the caller sends with a known id is updated in place; one with no id is inserted; one the caller OMITS is RETIRED, not deleted. The store cannot delete a line even if it wanted to: FK_PrepaidLedger_Line cascades, so dropping a prepaid_block would take the customer’s whole ledger with it and silently zero minutes they paid for, and FK_AgreementBillingRule_PrepaidLine would leave a prepaid rule violating its own CHECK.

The consequence for the console is the one worth stating: IT MUST ROUND-TRIP EVERY LINE’S publicId. A save that posts the lines back without their ids retires the whole set and inserts fresh rows, which loses every prepaid balance attached to them.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
billingDayinteger or null
defaultChargeTypeCoderequiredany

Arbitrary JSON.

defaultRateCentsinteger or null

int64.

endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namestring or null
notesstring or null
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnstring or null
templateIdstring or null
StatusBodyDescription
200application/json, AgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X PATCH 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"billingDay": 0,
"defaultChargeTypeCode": "string",
"defaultRateCents": 0,
"endsOn": "string",
"lines": [
{
"active": false,
"balanceMinutes": 0,
"description": "string",
"exclusive": false,
"filter": "string",
"includedDevicesPerSeat": 0,
"includedMinutes": 0,
"kind": "string",
"overageUnitAmountCents": 0,
"publicId": "string",
"seatRequiresEndpoint": false,
"sortOrder": 0,
"unitAmountCents": 0,
"usageMethod": "string"
}
],
"name": "string",
"notes": "string",
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
],
"startsOn": "string",
"templateId": "string"
}'

DELETE /api/v1/clients/{cid}/agreements/{agreementID}

Section titled “DELETE /api/v1/clients/{cid}/agreements/{agreementID}”

DELETEpsa:writemsp facemspIdempotent

Source: [object Object]

Delete agreement

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
204NoneSuccess, with no body.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X DELETE 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/clients/{cid}/agreements/{agreementID}/activate

Section titled “POST /api/v1/clients/{cid}/agreements/{agreementID}/activate”

POSTpsa:writemsp facemsp

Source: [object Object]

Activate agreement

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
200application/json, AgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GET /api/v1/clients/{cid}/agreements/{agreementID}/adjustments

Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}/adjustments”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

listPriceAdjustments lists the agreement’s discounts and escalators.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
200application/json, PagedResultPriceAdjustmentViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of PriceAdjustmentView
items[].activeboolean or null
items[].agreementLinePublicIdstring or null
items[].amountCentsrequiredinteger

int64.

items[].basisPointsrequiredinteger
items[].descriptionrequiredstring
items[].endsOnstring or null
items[].everyMonthsinteger or null
items[].kindrequiredstring
items[].moderequiredstring
items[].nextEffectiveOnstring or null
items[].noticeDaysinteger or null
items[].publicIdstring
items[].startsOnrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PUT /api/v1/clients/{cid}/agreements/{agreementID}/adjustments

Section titled “PUT /api/v1/clients/{cid}/agreements/{agreementID}/adjustments”

PUTpsa:writemsp facemspIdempotent

Source: [object Object]

putPriceAdjustments replaces the agreement’s discounts and escalators with the array sent.

Rows with a known publicId are updated, new rows created, rows left out removed. A new escalator’s first step is its startsOn; moving startsOn re-schedules it.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
adjustmentsrequiredarray of PriceAdjustmentView
adjustments[].activeboolean or null
adjustments[].agreementLinePublicIdstring or null
adjustments[].amountCentsrequiredinteger

int64.

adjustments[].basisPointsrequiredinteger
adjustments[].descriptionrequiredstring
adjustments[].endsOnstring or null
adjustments[].everyMonthsinteger or null
adjustments[].kindrequiredstring
adjustments[].moderequiredstring
adjustments[].nextEffectiveOnstring or null
adjustments[].noticeDaysinteger or null
adjustments[].publicIdstring
adjustments[].startsOnrequiredstring
StatusBodyDescription
200application/json, PagedResultPriceAdjustmentViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of PriceAdjustmentView
items[].activeboolean or null
items[].agreementLinePublicIdstring or null
items[].amountCentsrequiredinteger

int64.

items[].basisPointsrequiredinteger
items[].descriptionrequiredstring
items[].endsOnstring or null
items[].everyMonthsinteger or null
items[].kindrequiredstring
items[].moderequiredstring
items[].nextEffectiveOnstring or null
items[].noticeDaysinteger or null
items[].publicIdstring
items[].startsOnrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X PUT 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"adjustments": [
{
"active": false,
"agreementLinePublicId": "string",
"amountCents": 0,
"basisPoints": 0,
"description": "string",
"endsOn": "string",
"everyMonths": 0,
"kind": "string",
"mode": "string",
"nextEffectiveOn": "string",
"noticeDays": 0,
"publicId": "string",
"startsOn": "string"
}
]
}'

POST /api/v1/clients/{cid}/agreements/{agreementID}/end

Section titled “POST /api/v1/clients/{cid}/agreements/{agreementID}/end”

POSTpsa:writemsp facemsp

Source: [object Object]

End agreement

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
endsOnrequiredstring
StatusBodyDescription
200application/json, AgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"endsOn": "string"
}'

GET /api/v1/clients/{cid}/agreements/{agreementID}/estimate

Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}/estimate”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

getAgreementEstimate is the agreement’s next invoice before it exists: every line with the devices and people it counted, the change since the previous invoice, the devices no line bills, and what the engine could not settle.

It is computed by the invoice run’s own code, so it is the invoice the run would draft now.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
200application/json, AgreementEstimateViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdrequiredstring
countsTakenAtrequiredstring (date-time)
currencyCoderequiredstring
diffrequiredarray of EstimateDiffView
diff[].addedrequiredarray of EstimateMemberView
diff[].added[].idrequiredstring
diff[].added[].namerequiredstring
diff[].agreementLinePublicIdstring or null
diff[].deltaCentsrequiredinteger

int64.

diff[].descriptionrequiredstring
diff[].qtyAfterrequiredstring
diff[].qtyBeforestring or null
diff[].removedrequiredarray of EstimateMemberView
diff[].removed[].idrequiredstring
diff[].removed[].namerequiredstring
linesrequiredarray of EstimateLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].membersrequiredarray of EstimateMemberView
lines[].members[].idrequiredstring
lines[].members[].namerequiredstring
lines[].methodstring or null
lines[].quantityrequiredstring
lines[].unitAmountCentsrequiredinteger

int64.

periodEndrequiredstring
periodStartrequiredstring
previousInvoicePublicIdstring or null
proratedrequiredboolean
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

unassignedrequiredarray of EstimateMemberView
unassigned[].idrequiredstring
unassigned[].namerequiredstring
warningsrequiredarray of string
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GET /api/v1/clients/{cid}/agreements/{agreementID}/prepaid

Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}/prepaid”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List prepaid

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultPrepaidBlockViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of PrepaidBlockView
items[].balanceMinutesrequiredinteger
items[].descriptionrequiredstring
items[].includedMinutesrequiredinteger
items[].ledgerrequiredarray of PrepaidLedgerEntryView
items[].ledger[].invoicePublicIdstring or null
items[].ledger[].kindrequiredstring
items[].ledger[].minutesrequiredinteger
items[].ledger[].noterequiredstring
items[].ledger[].occurredAtrequiredstring (date-time)
items[].ledger[].publicIdrequiredstring
items[].ledger[].timeEntryPublicIdstring or null
items[].linePublicIdrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/clients/{cid}/agreements/{agreementID}/prepaid/{lineID}/purchase

Section titled “POST /api/v1/clients/{cid}/agreements/{agreementID}/prepaid/{lineID}/purchase”

POSTpsa:invoicemsp facemsp

Source: [object Object]

purchasePrepaid sells another block of minutes against an existing prepaid line.

psa:invoice and not psa:write: this changes what a customer owes. It writes a LEDGER ROW and nothing else, because the balance is the sum of the ledger and there is no cached copy to keep in step. Billing the purchase is the invoice run’s job through the agreement’s prepaid_block line; adding minutes here does not raise a charge, which is why the body has no price on it.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
lineIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
minutesrequiredinteger
noterequiredstring
StatusBodyDescription
200application/json, PrepaidBlockViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
balanceMinutesrequiredinteger
descriptionrequiredstring
includedMinutesrequiredinteger
ledgerrequiredarray of PrepaidLedgerEntryView
ledger[].invoicePublicIdstring or null
ledger[].kindrequiredstring
ledger[].minutesrequiredinteger
ledger[].noterequiredstring
ledger[].occurredAtrequiredstring (date-time)
ledger[].publicIdrequiredstring
ledger[].timeEntryPublicIdstring or null
linePublicIdrequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"minutes": 0,
"note": "string"
}'

GET /api/v1/clients/{cid}/agreements/{agreementID}/prices

Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}/prices”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

listLinePrices is the agreement’s effective-dated price history, by line then date.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes
StatusBodyDescription
200application/json, PagedResultLinePriceViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of LinePriceView
items[].agreementLinePublicIdrequiredstring
items[].effectiveOnrequiredstring
items[].inForcerequiredboolean
items[].noticeQueuedAtstring (date-time) or null
items[].overageUnitAmountCentsrequiredinteger

int64.

items[].publicIdrequiredstring
items[].scheduledOnrequiredstring
items[].sourceAdjustmentPublicIdstring or null
items[].unitAmountCentsrequiredinteger

int64.

pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PUT /api/v1/clients/{cid}/agreements/{agreementID}/rules

Section titled “PUT /api/v1/clients/{cid}/agreements/{agreementID}/rules”

PUTpsa:writemsp facemspIdempotent

Source: [object Object]

putAgreementRules replaces the whole ordered rule set and then reprices the unbilled work it governs.

TWO STEPS AND NOT ONE, and the split is the store’s instruction rather than this handler’s preference: ReplaceAgreementRules holds a transaction while it renumbers, and repricing is a loop over rows that may take a while. Doing both inside that transaction would block every technician logging time against this client for the length of the loop.

A REPRICE FAILURE DOES NOT FAIL THE SAVE. The rules are already committed by the time the loop runs, so returning an error would report a failure for work that succeeded and invite a retry that re-saved rules that were already there. Entries that could not be repriced are counted and logged, and the next invoice run classifies anything still unclassified anyway.

NameInTypeRequiredDescription
agreementIDpathstringYesA uuid.
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
StatusBodyDescription
200application/json, AgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activatedAtstring (date-time) or null
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

endedAtstring (date-time) or null
endsOnstring or null
linesrequiredarray of AgreementLineView
lines[].activerequiredboolean
lines[].balanceMinutesinteger or null
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
notesrequiredstring
prepaidBalanceMinutesinteger or null
publicIdrequiredstring
quotePublicIdstring or null
recurringCentsrequiredinteger

int64.

replacesAgreementPublicIdstring or null
ruleCountrequiredinteger
rulesrequiredarray of BillingRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
startsOnrequiredstring
statusrequiredstring
templatePublicIdstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X PUT 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
]
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

Get billing profile

NameInTypeRequiredDescription
cidpathstringYes
StatusBodyDescription
200application/json, ClientBillingProfileViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
addressrequiredstring
clientPublicIdrequiredstring
contactEmailrequiredstring
contactNamerequiredstring
netDaysinteger or null
taxRateBasisPointsinteger or null
timeZonestring or null
usageMethodstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/billing-profile' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PUTpsa:writemsp facemspIdempotent

Source: [object Object]

Put billing profile

NameInTypeRequiredDescription
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
addressrequiredstring
contactEmailrequiredstring
contactNamerequiredstring
netDaysinteger or null
taxRateBasisPointsinteger or null
timeZonestring or null
usageMethodrequiredany

Arbitrary JSON.

StatusBodyDescription
200application/json, ClientBillingProfileViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
addressrequiredstring
clientPublicIdrequiredstring
contactEmailrequiredstring
contactNamerequiredstring
netDaysinteger or null
taxRateBasisPointsinteger or null
timeZonestring or null
usageMethodstring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X PUT 'https://depot.example.com/api/v1/clients/<cid>/billing-profile' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"address": "string",
"contactEmail": "string",
"contactName": "string",
"netDays": 0,
"taxRateBasisPoints": 0,
"timeZone": "string",
"usageMethod": "string"
}'

POST /api/v1/clients/{cid}/invoices/generate

Section titled “POST /api/v1/clients/{cid}/invoices/generate”

POSTpsa:invoicemsp facemsp

Source: [object Object]

generateInvoice runs the invoice run for one client, right now.

It calls billingworker through PsaDeps.GenerateNow rather than re-implementing the run: the run holds the claim discipline, the proration and the two-commit ordering, and a second implementation of any of those is a second answer to what a month costs.

409 WHEN THE PERIOD IS ALREADY LIVE. On a multi-node deployment that is an ordinary outcome and not a fault: another node got there first and the draft it made is the one to open.

NameInTypeRequiredDescription
cidpathstringYes

Required, sent as application/json.

FieldTypeDescription
periodStartrequiredstring
StatusBodyDescription
201application/json, InvoiceViewCreated.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/invoices/generate' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"periodStart": "string"
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

listTicketTypes answers the vocabulary a billing rule may match on.

It is NOT paged, deliberately, unlike every other list on this face. A tenant’s ticket types are a handful of rows that fill one select, and a picker that could be truncated by a page size is a picker that silently offers fewer options than the desk actually has, which reads to an operator as the type they wanted not existing.

Soft-deleted types are excluded and the order is SortOrder then Code, both by the store’s own query: a retired type is not something a NEW rule should match on, while an existing rule that already names one keeps working, because the reference is by code and has no foreign key.

NameInTypeRequiredDescription
cidpathstringYes
StatusBodyDescription
200application/json, TicketTypeListViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of TicketTypeOptionView
items[].coderequiredstring
items[].namerequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/ticket-types' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/clients/{cid}/tickets/{ticketID}/time/{entryID}/reprice

Section titled “POST /api/v1/clients/{cid}/tickets/{ticketID}/time/{entryID}/reprice”

POSTpsa:writemsp facemspIdempotent

Source: [object Object]

repriceTimeEntry re-runs the agreement’s rules over one logged entry and restamps what they decide.

psa:write and NOT ticket:write, unlike the PATCH and DELETE beside it: editing what you logged is part of working a ticket, and changing what a customer is charged for it is not.

409 ONCE THE ENTRY IS INVOICED, which is the store’s ErrConflict under a row lock rather than a check here. The check and the write cannot race a finalize that way, and repricing work a customer has already been billed for makes a sent invoice disagree with the database.

NameInTypeRequiredDescription
cidpathstringYes
entryIDpathstringYesA uuid.
ticketIDpathstringYesA uuid.
StatusBodyDescription
200application/json, TicketTimeEntryViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo client matches that id.
409application/problem+json, ProblemDetailsThis client has no active agreement, so there are no rules to price this work against.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
amountCentsinteger

int64.

billablerequiredboolean
chargeTypeCodestring
classifiedAtstring (date-time) or null
invoicePublicIdstring or null
invoicedrequiredboolean
lockedboolean
messagePublicIdstring
minutesrequiredinteger
noterequiredstring
occurredAtrequiredstring (date-time)
outcomestring
publicIdrequiredstring
rateCentsSnapshotinteger or null

int64.

technicianNamerequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X POST 'https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List client unbilled time

NameInTypeRequiredDescription
cidpathstringYes
outcomequerystring, one of covered, excluded, fixed, payg, prepaid, unclassifiedNoComma-separated set. An unrecognised value is refused.
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
projectquerystringNo
subjectquerystringNo
StatusBodyDescription
200application/json, PagedResultUnbilledTimeEntryViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of UnbilledTimeEntryView
items[].amountCentsrequiredinteger

int64.

items[].billablerequiredboolean
items[].chargeTypeCoderequiredstring
items[].classifiedAtstring (date-time) or null
items[].clientNamerequiredstring
items[].clientPublicIdrequiredstring
items[].invoicePublicIdstring or null
items[].noterequiredstring
items[].outcomestring
items[].projectPublicIdrequiredstring
items[].projectReferencerequiredstring
items[].publicIdrequiredstring
items[].rateCentsSnapshotinteger or null

int64.

items[].secondsrequiredinteger
items[].startedAtrequiredstring (date-time)
items[].taskPublicIdrequiredstring
items[].taskTitlerequiredstring
items[].ticketPublicIdrequiredstring
items[].ticketReferencerequiredstring
items[].userPublicIdrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/unbilled-time?outcome=covered&page=0&pageSize=0&project=<project>&subject=<subject>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

listClientUsage is the client’s daily billable inventory between from and to (inclusive dates; default the last 31 days), oldest first, paged.

NameInTypeRequiredDescription
cidpathstringYes
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultUsageDayViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo client matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of UsageDayView
items[].carriedrequiredboolean
items[].daterequiredstring
items[].endpointCountrequiredinteger
items[].endpointsrequiredarray of UsageSnapshotEndpoint
items[].endpoints[].classrequiredstring
items[].endpoints[].groupsrequiredarray of string
items[].endpoints[].idrequiredstring
items[].endpoints[].osrequiredstring
items[].endpoints[].osDisplayrequiredstring
items[].endpoints[].rolesrequiredarray of string
items[].endpoints[].siterequiredstring
items[].takenAtrequiredstring (date-time)
items[].userCountrequiredinteger
items[].usersrequiredarray of UsageSnapshotUser
items[].users[].endpointIdsrequiredarray of string
items[].users[].idrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/clients/<cid>/usage?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List all agreements

NameInTypeRequiredDescription
clientquerystringNo
clientIdquerystringNo
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
statusquerystring, one of active, draft, endedNoComma-separated set. An unrecognised value is refused.
StatusBodyDescription
200application/json, PsaFanOutPagePracticeAgreementViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
500application/problem+json, ProblemDetailsCould not list clients.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of PracticeAgreementView
items[].billingDayrequiredinteger
items[].clientNamerequiredstring
items[].clientPublicIdrequiredstring
items[].defaultChargeTypeCodestring or null
items[].defaultRateCentsrequiredinteger

int64.

items[].endsOnstring or null
items[].namerequiredstring
items[].prepaidBalanceMinutesinteger or null
items[].publicIdrequiredstring
items[].recurringCentsrequiredinteger

int64.

items[].startsOnrequiredstring
items[].statusrequiredstring
items[].templatePublicIdstring or null
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
totalClientsrequiredinteger
unavailableClientsrequiredarray of string
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/agreements?client=<client>&clientId=<clientId>&page=0&pageSize=0&status=active' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

arAging answers what is owed, bucketed by how far past due it is.

THE TOTALS ARE SUMMED HERE AND NOT ASKED OF THE STORE. The buckets are disjoint by construction, so the footer is the column sums of the rows the screen is showing, and a separately-queried total is one that can disagree with the table above it after a payment lands between the two reads.

StatusBodyDescription
200application/json, ArAgingViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
asOfrequiredstring
currencyCoderequiredstring
rowsrequiredarray of ArAgingRowView
rows[].clientNamerequiredstring
rows[].clientPublicIdrequiredstring
rows[].currentCentsrequiredinteger

int64.

rows[].days30Centsrequiredinteger

int64.

rows[].days60Centsrequiredinteger

int64.

rows[].days90Centsrequiredinteger

int64.

rows[].over90Centsrequiredinteger

int64.

rows[].totalCentsrequiredinteger

int64.

totalsrequiredArAgingTotalsView

arAgingTotalsView is the footer row: the same five buckets with no client on them, which is what Omit<ArAgingRow, 'clientPublicId' | 'clientName'> means on the console side.

totals.currentCentsrequiredinteger

int64.

totals.days30Centsrequiredinteger

int64.

totals.days60Centsrequiredinteger

int64.

totals.days90Centsrequiredinteger

int64.

totals.over90Centsrequiredinteger

int64.

totals.totalCentsrequiredinteger

int64.

Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/ar-aging' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List charge types

NameInTypeRequiredDescription
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultChargeTypeViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of ChargeTypeView
items[].activerequiredboolean
items[].coderequiredstring
items[].defaultRateCentsrequiredinteger

int64.

items[].namerequiredstring
items[].publicIdrequiredstring
items[].sortOrderrequiredinteger
items[].unitrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/charge-types?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POSTpsa:adminmsp facemsp

Source: [object Object]

Create charge type

Required, sent as application/json.

FieldTypeDescription
coderequiredstring
defaultRateCentsrequiredinteger

int64.

namerequiredstring
sortOrderinteger or null
unitrequiredstring
StatusBodyDescription
201application/json, ChargeTypeViewCreated.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
activerequiredboolean
coderequiredstring
defaultRateCentsrequiredinteger

int64.

namerequiredstring
publicIdrequiredstring
sortOrderrequiredinteger
unitrequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/charge-types' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"code": "string",
"defaultRateCents": 0,
"name": "string",
"sortOrder": 0,
"unit": "string"
}'

PATCHpsa:adminmsp facemsp

Source: [object Object]

Patch charge type

NameInTypeRequiredDescription
codepathstringYes

Required, sent as application/json.

FieldTypeDescription
activeboolean or null
defaultRateCentsinteger or null

int64.

namestring or null
sortOrderinteger or null
unitstring or null
StatusBodyDescription
200application/json, ChargeTypeViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo charge type matches that code.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
activerequiredboolean
coderequiredstring
defaultRateCentsrequiredinteger

int64.

namerequiredstring
publicIdrequiredstring
sortOrderrequiredinteger
unitrequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X PATCH 'https://depot.example.com/api/v1/psa/charge-types/<code>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"active": false,
"defaultRateCents": 0,
"name": "string",
"sortOrder": 0,
"unit": "string"
}'

DELETEpsa:adminmsp facemspIdempotent

Source: [object Object]

deleteChargeType RETIRES a charge type.

There is no delete, deliberately: a code on a historical time entry has to keep resolving, so the row stays and Active goes false. The verb is DELETE because that is what the console’s button means, and the answer is the 204 a delete gives.

NameInTypeRequiredDescription
codepathstringYes
StatusBodyDescription
204NoneSuccess, with no body.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X DELETE 'https://depot.example.com/api/v1/psa/charge-types/<code>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:adminmsp facemspIdempotentdownloadNot fully resolved

What this page does not know
  • response

These are parts of the route the extractor could not read from the source. Everything else on this page came straight from it.

Source: [object Object]

exportCSV writes the finalized, non-void invoices of a window in the named accounting package’s own column order.

psa:admin and not psa:read, deliberately: one file carries every customer’s name, address, invoice number and amount, which is a different disclosure from reading one invoice on a screen.

The body is BUILT IN MEMORY BEFORE ANYTHING IS WRITTEN, so a failure halfway through becomes a problem document rather than a truncated CSV with a 200 on it. A half-written file imported into an accounting package is worse than no file, because nothing about it looks wrong.

NameInTypeRequiredDescription
formatquerystringNo
StatusBodyDescription
200application/octet-stream, string (binary)Success. The body shape was not resolved; see x-outpost-unresolved.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/export.csv?format=<format>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List invoices

NameInTypeRequiredDescription
clientquerystringNo
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
qquerystringNoFree-text search term.
sortquerystring, one of balance, due, issued, number, totalNoSort key.
statusquerystring, one of draft, issued, paid, partial, past_due, sent, voidNoComma-separated set. An unrecognised value is refused.
StatusBodyDescription
200application/json, PagedResultInvoiceSummaryViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of InvoiceSummaryView
items[].agreementPublicIdstring or null
items[].balanceCentsrequiredinteger

int64.

items[].clientNamerequiredstring
items[].clientPublicIdrequiredstring
items[].creditedCentsrequiredinteger

int64.

items[].currencyCoderequiredstring
items[].dueDatestring or null
items[].invoiceNumberstring or null
items[].issueDatestring or null
items[].paidCentsrequiredinteger

int64.

items[].periodEndstring or null
items[].periodStartstring or null
items[].projectPublicIdstring or null
items[].publicIdrequiredstring
items[].statusrequiredstring
items[].subtotalCentsrequiredinteger

int64.

items[].taxCentsrequiredinteger

int64.

items[].taxRateBasisPointsrequiredinteger
items[].totalCentsrequiredinteger

int64.

pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/invoices?client=<client>&page=0&pageSize=0&q=<q>&sort=balance&status=draft' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POSTpsa:invoicemsp facemsp

Source: [object Object]

createInvoice raises a DRAFT invoice for something that is not on a contract.

TWO CALLERS AND ONE OF THEM IS A WORKER. The console gets an “invoice something that is not on an agreement” it did not have, and the quote conversion worker needs it for an accepted quote’s one-time lines: acceptance creates a draft AGREEMENT for the recurring half and a draft INVOICE for the rest, and without this route the second half would have no door.

IT NAMES NO AGREEMENT AND NO PERIOD, so CreateDraftInvoice writes no BillingRunClaim row and nothing can collide with the month the invoice run is billing. That is the whole reason this is safe beside a running worker, and it is why the request type has no period fields to fill in by accident.

psa:invoice and not psa:write, matching finalize and send: the split in this package is that whoever writes a contract is not automatically whoever bills against it, and raising a document a customer will be asked to pay is billing.

Required, sent as application/json.

FieldTypeDescription
clientIdrequiredstring
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
taxRateBasisPointsinteger or null
StatusBodyDescription
201application/json, InvoiceViewCreated.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"clientId": "string",
"lines": [
{
"agreementLinePublicId": "string",
"amountCents": 0,
"changeOrderPublicId": "string",
"description": "string",
"kind": "string",
"milestonePublicId": "string",
"projectPublicId": "string",
"publicId": "string",
"quantity": "string",
"quantitySnapshot": {
"count": 0,
"ids": [
"string"
]
},
"sortOrder": 0,
"timeEntryPublicId": "string",
"unitAmountCents": 0
}
],
"notes": "string",
"taxRateBasisPoints": 0
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

Get invoice

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.
StatusBodyDescription
200application/json, InvoiceViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PATCHpsa:writemsp facemsp

Source: [object Object]

patchInvoice edits a DRAFT and nothing else.

The 409 past draft is the store’s, decided inside the transaction that takes the row FOR UPDATE, and NOT a status check here. A check here would race a concurrent finalize and let an edit land on a document that had already been numbered, rendered and sent.

A non-nil lines array REPLACES the whole set and the store recomputes subtotal, tax and total from the result. Omitting it leaves the lines alone, which is the difference between “I renamed the notes” and “this invoice has no lines”.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
linesarray of InvoiceLineView or null
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesstring or null
taxRateBasisPointsinteger or null
StatusBodyDescription
200application/json, InvoiceViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X PATCH 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"lines": [
{
"agreementLinePublicId": "string",
"amountCents": 0,
"changeOrderPublicId": "string",
"description": "string",
"kind": "string",
"milestonePublicId": "string",
"projectPublicId": "string",
"publicId": "string",
"quantity": "string",
"quantitySnapshot": {
"count": 0,
"ids": [
"string"
]
},
"sortOrder": 0,
"timeEntryPublicId": "string",
"unitAmountCents": 0
}
],
"notes": "string",
"taxRateBasisPoints": 0
}'

DELETEpsa:writemsp facemspIdempotent

Source: [object Object]

deleteInvoice discards a DRAFT and gives back everything it was holding.

DELETE IS FOR A DRAFT AND VOID IS FOR A DOCUMENT, and they are not two spellings of one act. A draft never reached a customer, has no number and has no rendered PDF, so there is nothing for an auditor to find a gap in and the row goes. A numbered invoice is a record: it keeps its row and its number forever, and the store answers ErrConflict here to say so.

AFTER THE CONTROL-DATABASE COMMIT, and only after it, the client database is put back, on the same two-commit shape as void:

  • DetachTimeEntriesFromDraft, not UnmarkTimeEntriesInvoiced. A draft stamps InvoicePublicID and never InvoicedAt, so detach is the method written for exactly this case; unmark would additionally clear an InvoicedAt that a draft’s entries cannot have, and reaching for it here would suggest they can. Entries whose InvoicedAt IS set are left alone by both.
  • ReversePrepaidForInvoice, because building the draft wrote purchase and drawdown rows against it. Leaving those behind would hand the customer minutes an invoice nobody ever sent had paid for.

THE PERIOD FREES ITSELF. Nothing here touches BillingRunClaim and nothing needs to: the claim’s upsert in CreateDraftInvoice reclaims any row whose InvoicePublicId names no live invoice, and after this the invoice is gone entirely, so the next generate-now for the same period takes the claim and passes UQ_Invoice_AgreementPeriod. The claim was never the real protection.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.
StatusBodyDescription
204NoneSuccess, with no body.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:write.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X DELETE 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/psa/invoices/{invoiceID}/credit-notes

Section titled “POST /api/v1/psa/invoices/{invoiceID}/credit-notes”

POSTpsa:invoicemsp facemsp

Source: [object Object]

createCreditNote reduces what a customer owes, with a number of its own.

amountCents IS THE TOTAL CREDITED, which is what the balance goes down by and what the operator typed into a box beside a balance. It is split into a subtotal and a tax component at the invoice’s OWN snapshotted rate, so the credit reconciles against the document it reduces line for line; the split is exact by construction, since the tax is the remainder rather than a second rounding.

The store refuses a credit larger than the outstanding balance with ErrConflict: crediting more than is owed is a refund, which this slice does not have.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
amountCentsrequiredinteger

int64.

kindrequiredstring
reasonrequiredstring
StatusBodyDescription
201application/json, CreditNoteViewCreated.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
creditNoteNumberrequiredstring
issueDaterequiredstring
kindrequiredstring
publicIdrequiredstring
reasonrequiredstring
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

totalCentsrequiredinteger

int64.

Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"amountCents": 0,
"kind": "string",
"reason": "string"
}'

POST /api/v1/psa/invoices/{invoiceID}/finalize

Section titled “POST /api/v1/psa/invoices/{invoiceID}/finalize”

POSTpsa:invoicemsp facemsp

Source: [object Object]

finalizeInvoice turns a draft into a numbered, rendered, frozen document, then marks the work it billed as invoiced in the client’s database.

EVERY INPUT THE RENDERER NEEDS IS GATHERED BEFORE THE STORE CALL, because the render closure runs INSIDE the store’s transaction and must be pure: a database call from it would deadlock against the row it is rendering.

IDEMPOTENT. Called on an invoice already past draft the store returns it unchanged with no error, so a retried request after a timeout nobody saw the answer to cannot double-number a document. The marking below runs on that path too, which is deliberate: a retry is exactly when the first attempt’s second commit is the one that went missing.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.
StatusBodyDescription
200application/json, InvoiceViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GET /api/v1/psa/invoices/{invoiceID}/payments

Section titled “GET /api/v1/psa/invoices/{invoiceID}/payments”

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List payments

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultPaymentViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of PaymentView
items[].amountCentsrequiredinteger

int64.

items[].methodrequiredstring
items[].noterequiredstring
items[].publicIdrequiredstring
items[].receivedOnrequiredstring
items[].referencerequiredstring
items[].voidedAtstring (date-time) or null
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/psa/invoices/{invoiceID}/payments

Section titled “POST /api/v1/psa/invoices/{invoiceID}/payments”

POSTpsa:invoicemsp facemsp

Source: [object Object]

createPayment records money that arrived.

The store recomputes the invoice’s status in the SAME transaction, so a bill can never be paid in full and still read past_due. A zero or negative payment is refused here and by CHECK: a refund is a credit note and a zero payment is a typo.

AN OVERPAYMENT IS ALSO REFUSED, by the store, as ErrConflict. The status walk reads any non-positive balance as paid, so money beyond the total would drive the balance negative and disappear from every report rather than landing somewhere anybody could refund it, and this slice has no refund. The 409 below names the balance so an operator can correct the figure rather than guess at which of the two conflicts they hit.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
amountCentsrequiredinteger

int64.

methodrequiredstring
noterequiredstring
receivedOnrequiredstring
referencerequiredstring
StatusBodyDescription
201application/json, PaymentViewCreated.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
amountCentsrequiredinteger

int64.

methodrequiredstring
noterequiredstring
publicIdrequiredstring
receivedOnrequiredstring
referencerequiredstring
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"amountCents": 0,
"method": "string",
"note": "string",
"receivedOn": "string",
"reference": "string"
}'

GETpsa:readmsp facemspIdempotentdownloadNot fully resolved

What this page does not know
  • response

These are parts of the route the extractor could not read from the source. Everything else on this page came straight from it.

Source: [object Object]

getInvoicePdf serves the bytes stored at finalize.

404 FOR A DRAFT, which has no row in InvoicePdf. Rendering one on demand would hand somebody a document that looks like an invoice, carries no number, and would be a different document tomorrow.

Content-Disposition is INLINE and not attachment, unlike the report downloads: the console opens this in a tab so the browser’s own viewer renders it, with the browser’s own save control beside it.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.
StatusBodyDescription
200application/octet-stream, string (binary)Success. The body shape was not resolved; see x-outpost-unresolved.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POST /api/v1/psa/invoices/{invoiceID}/send

Section titled “POST /api/v1/psa/invoices/{invoiceID}/send”

POSTpsa:invoicemsp facemspIdempotent

Source: [object Object]

sendInvoice enqueues the document to the client’s accounts-payable contact.

IT DOES NOT MOVE THE STATUS. The mail drain calls MarkInvoiceSent after the message actually leaves, which is what makes “did we send it” answerable without reading the outbox. Stamping ‘sent’ here would say yes for a message still sitting in a queue behind a broken SMTP password.

A DRAFT CANNOT BE SENT: it has no number and no rendered document, so there is nothing to attach and nothing for the customer to quote back.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
torequiredstring
StatusBodyDescription
200application/json, InvoiceViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches that id.
409application/problem+json, ProblemDetailsA draft has no number and no document. Finalize it first.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"to": "string"
}'

POST /api/v1/psa/invoices/{invoiceID}/void

Section titled “POST /api/v1/psa/invoices/{invoiceID}/void”

POSTpsa:invoicemsp facemsp

Source: [object Object]

voidInvoice cancels a document, keeps its number, and gives the work back.

THE ROW AND THE NUMBER STAY FOREVER. A document that can disappear is not a record, and a gap in the series is what an auditor asks about. Voiding does free the PERIOD, because UQ_Invoice_AgreementPeriod excludes void, so a replacement can be drafted for the same month.

After the control-database commit, and only after it, the client database is put back: the time entries are unstamped so the work returns to the unbilled queue, and every prepaid drawdown this invoice caused is reversed with a compensating adjustment. Both are idempotent, because the reconciliation sweep can reach the same rows.

NameInTypeRequiredDescription
invoiceIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
reasonrequiredstring
StatusBodyDescription
200application/json, InvoiceViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:invoice.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
agreementPublicIdstring or null
balanceCentsrequiredinteger

int64.

clientNamerequiredstring
clientPublicIdrequiredstring
creditNotesrequiredarray of CreditNoteView
creditNotes[].creditNoteNumberrequiredstring
creditNotes[].issueDaterequiredstring
creditNotes[].kindrequiredstring
creditNotes[].publicIdrequiredstring
creditNotes[].reasonrequiredstring
creditNotes[].statusrequiredstring
creditNotes[].subtotalCentsrequiredinteger

int64.

creditNotes[].taxCentsrequiredinteger

int64.

creditNotes[].totalCentsrequiredinteger

int64.

creditedCentsrequiredinteger

int64.

currencyCoderequiredstring
dueDatestring or null
finalizedAtstring (date-time) or null
invoiceNumberstring or null
issueDatestring or null
linesrequiredarray of InvoiceLineView
lines[].agreementLinePublicIdstring or null
lines[].amountCentsrequiredinteger

int64.

lines[].changeOrderPublicIdstring or null
lines[].descriptionrequiredstring
lines[].kindrequiredstring
lines[].milestonePublicIdstring or null
lines[].projectPublicIdstring or null
lines[].publicIdstring
lines[].quantityrequiredstring
lines[].quantitySnapshotQuantitySnapshotView
lines[].quantitySnapshot.countrequiredinteger
lines[].quantitySnapshot.idsrequiredarray of string
lines[].sortOrderrequiredinteger
lines[].timeEntryPublicIdstring or null
lines[].unitAmountCentsrequiredinteger

int64.

notesrequiredstring
paidCentsrequiredinteger

int64.

paymentsrequiredarray of PaymentView
payments[].amountCentsrequiredinteger

int64.

payments[].methodrequiredstring
payments[].noterequiredstring
payments[].publicIdrequiredstring
payments[].receivedOnrequiredstring
payments[].referencerequiredstring
payments[].voidedAtstring (date-time) or null
periodEndstring or null
periodStartstring or null
projectPublicIdstring or null
publicIdrequiredstring
remitTorequiredstring
sentAtstring (date-time) or null
statusrequiredstring
subtotalCentsrequiredinteger

int64.

taxCentsrequiredinteger

int64.

taxRateBasisPointsrequiredinteger
totalCentsrequiredinteger

int64.

voidReasonstring or null
voidedAtstring (date-time) or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"reason": "string"
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

readyToInvoice answers which clients have a period worth billing and what it comes to.

EVERY FIGURE IS PRODUCED BY billing.BuildDraft, the same pure function the invoice run uses, so the number on this screen and the number on the draft that follows it cannot disagree. Summing the lines here instead would be a second implementation of proration, and an operator reading two answers to “what is this month worth” would be right to trust neither.

A client with no active agreement, or whose billing day has not arrived, is simply absent. It is a work queue, not a roster.

NameInTypeRequiredDescription
clientquerystringNo
clientIdquerystringNo
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PsaFanOutPageReadyToInvoiceClientViewSuccess.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
500application/problem+json, ProblemDetailsCould not list clients.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of ReadyToInvoiceClientView
items[].agreementPublicIdrequiredstring
items[].clientNamerequiredstring
items[].clientPublicIdrequiredstring
items[].draftInvoicePublicIdstring or null
items[].entryCountrequiredinteger
items[].lineCountrequiredinteger
items[].periodEndrequiredstring
items[].periodStartrequiredstring
items[].recurringCentsrequiredinteger

int64.

items[].timeCentsrequiredinteger

int64.

items[].totalCentsrequiredinteger

int64.

items[].unbilledMinutesrequiredinteger
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
totalClientsrequiredinteger
unavailableClientsrequiredarray of string
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/ready-to-invoice?client=<client>&clientId=<clientId>&page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

Get settings

StatusBodyDescription
200application/json, BillingSettingsViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
currencyCoderequiredstring
invoicePrefixrequiredstring
netDaysrequiredinteger
overdueReminderEveryDaysrequiredinteger
prepaidLowThresholdMinutesrequiredinteger
reminderDaysBeforerequiredinteger
remitTorequiredstring
taxRateBasisPointsrequiredinteger
usageMethodrequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/settings' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PUTpsa:adminmsp facemspIdempotent

Source: [object Object]

Put settings

Required, sent as application/json.

FieldTypeDescription
currencyCoderequiredstring
invoicePrefixrequiredstring
netDaysrequiredinteger
overdueReminderEveryDaysrequiredinteger
prepaidLowThresholdMinutesrequiredinteger
reminderDaysBeforerequiredinteger
remitTorequiredstring
taxRateBasisPointsrequiredinteger
usageMethodrequiredstring
StatusBodyDescription
200application/json, BillingSettingsViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
currencyCoderequiredstring
invoicePrefixrequiredstring
netDaysrequiredinteger
overdueReminderEveryDaysrequiredinteger
prepaidLowThresholdMinutesrequiredinteger
reminderDaysBeforerequiredinteger
remitTorequiredstring
taxRateBasisPointsrequiredinteger
usageMethodrequiredstring
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X PUT 'https://depot.example.com/api/v1/psa/settings' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"currencyCode": "string",
"invoicePrefix": "string",
"netDays": 0,
"overdueReminderEveryDays": 0,
"prepaidLowThresholdMinutes": 0,
"reminderDaysBefore": 0,
"remitTo": "string",
"taxRateBasisPoints": 0,
"usageMethod": "string"
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List templates

NameInTypeRequiredDescription
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
StatusBodyDescription
200application/json, PagedResultAgreementTemplateViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of AgreementTemplateView
items[].billingDayrequiredinteger
items[].defaultChargeTypeCodestring or null
items[].defaultRateCentsrequiredinteger

int64.

items[].descriptionrequiredstring
items[].linesrequiredarray of AgreementTemplateLineView
items[].lines[].descriptionrequiredstring
items[].lines[].exclusiveboolean or null
items[].lines[].filterany

Arbitrary JSON.

items[].lines[].includedDevicesPerSeatinteger or null
items[].lines[].includedMinutesrequiredinteger
items[].lines[].kindrequiredstring
items[].lines[].overageUnitAmountCentsinteger or null

int64.

items[].lines[].publicIdstring
items[].lines[].seatRequiresEndpointboolean or null
items[].lines[].sortOrderrequiredinteger
items[].lines[].unitAmountCentsrequiredinteger

int64.

items[].lines[].usageMethodany

Arbitrary JSON.

items[].namerequiredstring
items[].publicIdrequiredstring
items[].rulesrequiredarray of AgreementTemplateRuleView
items[].rules[].chargeTypeCodestring or null
items[].rules[].descriptionrequiredstring
items[].rules[].outOfHoursboolean or null
items[].rules[].outcomerequiredstring
items[].rules[].positionrequiredinteger
items[].rules[].prepaidTemplateLinePublicIdstring or null
items[].rules[].projectWorkboolean or null
items[].rules[].publicIdstring
items[].rules[].rateCentsinteger or null

int64.

items[].rules[].ticketTypeCodestring or null
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/templates?page=0&pageSize=0' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

POSTpsa:adminmsp facemsp

Source: [object Object]

Create template

Required, sent as application/json.

FieldTypeDescription
billingDayinteger or null
defaultChargeTypeCoderequiredany

Arbitrary JSON.

defaultRateCentsinteger or null

int64.

descriptionstring or null
linesrequiredarray of AgreementTemplateLineView
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namestring or null
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
StatusBodyDescription
201application/json, AgreementTemplateViewCreated.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

201 response body

FieldTypeDescription
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

descriptionrequiredstring
linesrequiredarray of AgreementTemplateLineView
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
publicIdrequiredstring
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X POST 'https://depot.example.com/api/v1/psa/templates' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"billingDay": 0,
"defaultChargeTypeCode": "string",
"defaultRateCents": 0,
"description": "string",
"lines": [
{
"description": "string",
"exclusive": false,
"filter": "string",
"includedDevicesPerSeat": 0,
"includedMinutes": 0,
"kind": "string",
"overageUnitAmountCents": 0,
"publicId": "string",
"seatRequiresEndpoint": false,
"sortOrder": 0,
"unitAmountCents": 0,
"usageMethod": "string"
}
],
"name": "string",
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidTemplateLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
]
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

Get template

NameInTypeRequiredDescription
templateIDpathstringYesA uuid.
StatusBodyDescription
200application/json, AgreementTemplateViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

descriptionrequiredstring
linesrequiredarray of AgreementTemplateLineView
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
publicIdrequiredstring
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/templates/<templateID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PATCHpsa:adminmsp facemsp

Source: [object Object]

patchTemplate merges the body onto the stored row and writes the result whole.

A read-modify-write rather than a partial UPDATE because the store method replaces the template and its LINE SET together in one transaction, which is the right shape for the writer and the wrong one for a PATCH that names three fields. Omitting lines leaves the existing set exactly as it is; sending an empty array clears it, which is the difference between “I did not touch the lines” and “this template has none”.

NameInTypeRequiredDescription
templateIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
billingDayinteger or null
defaultChargeTypeCoderequiredany

Arbitrary JSON.

defaultRateCentsinteger or null

int64.

descriptionstring or null
linesrequiredarray of AgreementTemplateLineView
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namestring or null
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
StatusBodyDescription
200application/json, AgreementTemplateViewSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
billingDayrequiredinteger
defaultChargeTypeCodestring or null
defaultRateCentsrequiredinteger

int64.

descriptionrequiredstring
linesrequiredarray of AgreementTemplateLineView
lines[].descriptionrequiredstring
lines[].exclusiveboolean or null
lines[].filterany

Arbitrary JSON.

lines[].includedDevicesPerSeatinteger or null
lines[].includedMinutesrequiredinteger
lines[].kindrequiredstring
lines[].overageUnitAmountCentsinteger or null

int64.

lines[].publicIdstring
lines[].seatRequiresEndpointboolean or null
lines[].sortOrderrequiredinteger
lines[].unitAmountCentsrequiredinteger

int64.

lines[].usageMethodany

Arbitrary JSON.

namerequiredstring
publicIdrequiredstring
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
curl -X PATCH 'https://depot.example.com/api/v1/psa/templates/<templateID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"billingDay": 0,
"defaultChargeTypeCode": "string",
"defaultRateCents": 0,
"description": "string",
"lines": [
{
"description": "string",
"exclusive": false,
"filter": "string",
"includedDevicesPerSeat": 0,
"includedMinutes": 0,
"kind": "string",
"overageUnitAmountCents": 0,
"publicId": "string",
"seatRequiresEndpoint": false,
"sortOrder": 0,
"unitAmountCents": 0,
"usageMethod": "string"
}
],
"name": "string",
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidTemplateLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
]
}'

DELETEpsa:adminmsp facemspIdempotent

Source: [object Object]

Delete template

NameInTypeRequiredDescription
templateIDpathstringYesA uuid.
StatusBodyDescription
204NoneSuccess, with no body.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X DELETE 'https://depot.example.com/api/v1/psa/templates/<templateID>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

PUT /api/v1/psa/templates/{templateID}/rules

Section titled “PUT /api/v1/psa/templates/{templateID}/rules”

PUTpsa:adminmsp facemspIdempotent

Source: [object Object]

putTemplateRules replaces the whole ordered rule set on one template.

NOTHING IS REPRICED AFTERWARDS and nothing can be. That is the one difference from putAgreementRules, which reclassifies the unbilled work its rules govern: these rows govern no work at all until somebody builds an agreement from the template, and the agreement gets a copy.

psa:admin and not psa:write, matching every other template write: the rate card is a practice-wide document and editing it is not the same permission as editing one customer’s contract.

NameInTypeRequiredDescription
templateIDpathstringYesA uuid.

Required, sent as application/json.

FieldTypeDescription
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
StatusBodyDescription
200application/json, AgreementTemplateRulesRequestSuccess.
400application/problem+json, ProblemDetailsThe request body is not valid JSON.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:admin.
404application/problem+json, ProblemDetailsNo resource matches that id.
500application/problem+json, ProblemDetailsThe request failed.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
rulesrequiredarray of AgreementTemplateRuleView
rules[].chargeTypeCodestring or null
rules[].descriptionrequiredstring
rules[].outOfHoursboolean or null
rules[].outcomerequiredstring
rules[].positionrequiredinteger
rules[].prepaidTemplateLinePublicIdstring or null
rules[].projectWorkboolean or null
rules[].publicIdstring
rules[].rateCentsinteger or null

int64.

rules[].ticketTypeCodestring or null
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X PUT 'https://depot.example.com/api/v1/psa/templates/<templateID>/rules' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-b 'outpost_console=<console-session>' \
--data '{
"rules": [
{
"chargeTypeCode": "string",
"description": "string",
"outOfHours": false,
"outcome": "string",
"position": 0,
"prepaidTemplateLinePublicId": "string",
"projectWork": false,
"publicId": "string",
"rateCents": 0,
"ticketTypeCode": "string"
}
]
}'

GETpsa:readmsp facemspIdempotent

Source: [object Object]

List unbilled time

NameInTypeRequiredDescription
clientquerystringNo
clientIdquerystringNo
outcomequerystring, one of covered, excluded, fixed, payg, prepaid, unclassifiedNoComma-separated set. An unrecognised value is refused.
pagequeryintegerNoOne-based page number. Defaults to 1.
pageSizequeryintegerNoRows per page, 1 to 200. Defaults to 50.
projectquerystringNo
subjectquerystringNo
StatusBodyDescription
200application/json, PsaFanOutPageUnbilledTimeEntryViewSuccess.
400application/problem+json, ProblemDetailsOne or more validation errors occurred.
401application/problem+json, ProblemDetailsNo signed-in identity.
403application/problem+json, ProblemDetailsThe identity does not hold psa:read.
500application/problem+json, ProblemDetailsCould not list clients.
503application/problem+json, ProblemDetailsClient billing is not available on this server.

200 response body

FieldTypeDescription
itemsrequiredarray of UnbilledTimeEntryView
items[].amountCentsrequiredinteger

int64.

items[].billablerequiredboolean
items[].chargeTypeCoderequiredstring
items[].classifiedAtstring (date-time) or null
items[].clientNamerequiredstring
items[].clientPublicIdrequiredstring
items[].invoicePublicIdstring or null
items[].noterequiredstring
items[].outcomestring
items[].projectPublicIdrequiredstring
items[].projectReferencerequiredstring
items[].publicIdrequiredstring
items[].rateCentsSnapshotinteger or null

int64.

items[].secondsrequiredinteger
items[].startedAtrequiredstring (date-time)
items[].taskPublicIdrequiredstring
items[].taskTitlerequiredstring
items[].ticketPublicIdrequiredstring
items[].ticketReferencerequiredstring
items[].userPublicIdrequiredstring
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
totalClientsrequiredinteger
unavailableClientsrequiredarray of string
Terminal window
# Sign in first. outpost_console is a session cookie, so send it with every request.
# This operation is idempotent. Repeating it does not repeat its effect.
curl -X GET 'https://depot.example.com/api/v1/psa/unbilled-time?client=<client>&clientId=<clientId>&outcome=covered&page=0&pageSize=0&project=<project>' \
-H 'X-CSRF-Token: <csrf-token>' \
-H 'Accept: application/json' \
-b 'outpost_console=<console-session>'

Content is licensed CC BY 4.0. Code samples are MIT. Outpost and the Outpost mark are trademarks of Outpost Business Solutions, PBC and are not covered by either license.