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.
GET /api/v1/clients/{cid}/agreements
Section titled “GET /api/v1/clients/{cid}/agreements”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List agreements
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultAgreementSummaryView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of AgreementSummaryView | |
items[].billingDayrequired | integer | |
items[].defaultChargeTypeCode | string or null | |
items[].defaultRateCentsrequired | integer | int64. |
items[].endsOn | string or null | |
items[].namerequired | string | |
items[].prepaidBalanceMinutes | integer or null | |
items[].publicIdrequired | string | |
items[].recurringCentsrequired | integer | int64. |
items[].startsOnrequired | string | |
items[].statusrequired | string | |
items[].templatePublicId | string or null | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/clients/<cid>/agreements");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/clients/{cid}/agreements
Section titled “POST /api/v1/clients/{cid}/agreements”POSTpsa:writemsp facemsp
Source: [object Object]
Create agreement
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
billingDay | integer or null | |
defaultChargeTypeCoderequired | any | Arbitrary JSON. |
defaultRateCents | integer or null | int64. |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
name | string or null | |
notes | string or null | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOn | string or null | |
templateId | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, AgreementView | Created. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/agreements", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/agreements");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/clients/{cid}/agreements/{agreementID}
Section titled “GET /api/v1/clients/{cid}/agreements/{agreementID}”GETpsa:readmsp facemspIdempotent
Source: [object Object]
Get agreement
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
billingDay | integer or null | |
defaultChargeTypeCoderequired | any | Arbitrary JSON. |
defaultRateCents | integer or null | int64. |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
name | string or null | |
notes | string or null | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOn | string or null | |
templateId | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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"}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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",}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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"}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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"}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/clients/{cid}/agreements/{agreementID}
Section titled “DELETE /api/v1/clients/{cid}/agreements/{agreementID}”DELETEpsa:writemsp facemspIdempotent
Source: [object Object]
Delete agreement
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.delete(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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>'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.post(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/activate");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultPriceAdjustmentView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of PriceAdjustmentView | |
items[].active | boolean or null | |
items[].agreementLinePublicId | string or null | |
items[].amountCentsrequired | integer | int64. |
items[].basisPointsrequired | integer | |
items[].descriptionrequired | string | |
items[].endsOn | string or null | |
items[].everyMonths | integer or null | |
items[].kindrequired | string | |
items[].moderequired | string | |
items[].nextEffectiveOn | string or null | |
items[].noticeDays | integer or null | |
items[].publicId | string | |
items[].startsOnrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
adjustmentsrequired | array of PriceAdjustmentView | |
adjustments[].active | boolean or null | |
adjustments[].agreementLinePublicId | string or null | |
adjustments[].amountCentsrequired | integer | int64. |
adjustments[].basisPointsrequired | integer | |
adjustments[].descriptionrequired | string | |
adjustments[].endsOn | string or null | |
adjustments[].everyMonths | integer or null | |
adjustments[].kindrequired | string | |
adjustments[].moderequired | string | |
adjustments[].nextEffectiveOn | string or null | |
adjustments[].noticeDays | integer or null | |
adjustments[].publicId | string | |
adjustments[].startsOnrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultPriceAdjustmentView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of PriceAdjustmentView | |
items[].active | boolean or null | |
items[].agreementLinePublicId | string or null | |
items[].amountCentsrequired | integer | int64. |
items[].basisPointsrequired | integer | |
items[].descriptionrequired | string | |
items[].endsOn | string or null | |
items[].everyMonths | integer or null | |
items[].kindrequired | string | |
items[].moderequired | string | |
items[].nextEffectiveOn | string or null | |
items[].noticeDays | integer or null | |
items[].publicId | string | |
items[].startsOnrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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" } ]}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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" } ]}'@
$response = Invoke-RestMethod ` -Method Put ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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", }, ],}
response = requests.put(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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" } ]}`)
req, err := http.NewRequest(http.MethodPut, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments";
const response = await fetch(url, { method: "PUT", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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" } ] }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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" } ]}""";
var request = new HttpRequestMessage(HttpMethod.Put, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/adjustments");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
endsOnrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "endsOn": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "endsOn": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "endsOn": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "endsOn": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "endsOn": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/end");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementEstimateView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicIdrequired | string | |
countsTakenAtrequired | string (date-time) | |
currencyCoderequired | string | |
diffrequired | array of EstimateDiffView | |
diff[].addedrequired | array of EstimateMemberView | |
diff[].added[].idrequired | string | |
diff[].added[].namerequired | string | |
diff[].agreementLinePublicId | string or null | |
diff[].deltaCentsrequired | integer | int64. |
diff[].descriptionrequired | string | |
diff[].qtyAfterrequired | string | |
diff[].qtyBefore | string or null | |
diff[].removedrequired | array of EstimateMemberView | |
diff[].removed[].idrequired | string | |
diff[].removed[].namerequired | string | |
linesrequired | array of EstimateLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].membersrequired | array of EstimateMemberView | |
lines[].members[].idrequired | string | |
lines[].members[].namerequired | string | |
lines[].method | string or null | |
lines[].quantityrequired | string | |
lines[].unitAmountCentsrequired | integer | int64. |
periodEndrequired | string | |
periodStartrequired | string | |
previousInvoicePublicId | string or null | |
proratedrequired | boolean | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
unassignedrequired | array of EstimateMemberView | |
unassigned[].idrequired | string | |
unassigned[].namerequired | string | |
warningsrequired | array of string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/estimate");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultPrepaidBlockView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of PrepaidBlockView | |
items[].balanceMinutesrequired | integer | |
items[].descriptionrequired | string | |
items[].includedMinutesrequired | integer | |
items[].ledgerrequired | array of PrepaidLedgerEntryView | |
items[].ledger[].invoicePublicId | string or null | |
items[].ledger[].kindrequired | string | |
items[].ledger[].minutesrequired | integer | |
items[].ledger[].noterequired | string | |
items[].ledger[].occurredAtrequired | string (date-time) | |
items[].ledger[].publicIdrequired | string | |
items[].ledger[].timeEntryPublicId | string or null | |
items[].linePublicIdrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes | |
lineID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
minutesrequired | integer | |
noterequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PrepaidBlockView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
balanceMinutesrequired | integer | |
descriptionrequired | string | |
includedMinutesrequired | integer | |
ledgerrequired | array of PrepaidLedgerEntryView | |
ledger[].invoicePublicId | string or null | |
ledger[].kindrequired | string | |
ledger[].minutesrequired | integer | |
ledger[].noterequired | string | |
ledger[].occurredAtrequired | string (date-time) | |
ledger[].publicIdrequired | string | |
ledger[].timeEntryPublicId | string or null | |
linePublicIdrequired | string |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "minutes": 0, "note": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "minutes": 0, "note": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "minutes": 0, "note": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "minutes": 0, "note": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "minutes": 0, "note": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prepaid/<lineID>/purchase");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultLinePriceView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of LinePriceView | |
items[].agreementLinePublicIdrequired | string | |
items[].effectiveOnrequired | string | |
items[].inForcerequired | boolean | |
items[].noticeQueuedAt | string (date-time) or null | |
items[].overageUnitAmountCentsrequired | integer | int64. |
items[].publicIdrequired | string | |
items[].scheduledOnrequired | string | |
items[].sourceAdjustmentPublicId | string or null | |
items[].unitAmountCentsrequired | integer | int64. |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/prices");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
agreementID | path | string | Yes | A uuid. |
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activatedAt | string (date-time) or null | |
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
endedAt | string (date-time) or null | |
endsOn | string or null | |
linesrequired | array of AgreementLineView | |
lines[].activerequired | boolean | |
lines[].balanceMinutes | integer or null | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
notesrequired | string | |
prepaidBalanceMinutes | integer or null | |
publicIdrequired | string | |
quotePublicId | string or null | |
recurringCentsrequired | integer | int64. |
replacesAgreementPublicId | string or null | |
ruleCountrequired | integer | |
rulesrequired | array of BillingRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null | |
startsOnrequired | string | |
statusrequired | string | |
templatePublicId | string or null |
Request samples
Section titled “Request samples”# 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" } ]}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}'@
$response = Invoke-RestMethod ` -Method Put ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": False, "outcome": "string", "position": 0, "prepaidLinePublicId": "string", "projectWork": False, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string", }, ],}
response = requests.put(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}`)
req, err := http.NewRequest(http.MethodPut, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules";
const response = await fetch(url, { method: "PUT", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ] }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}""";
var request = new HttpRequestMessage(HttpMethod.Put, "https://depot.example.com/api/v1/clients/<cid>/agreements/<agreementID>/rules");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/clients/{cid}/billing-profile
Section titled “GET /api/v1/clients/{cid}/billing-profile”GETpsa:readmsp facemspIdempotent
Source: [object Object]
Get billing profile
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, ClientBillingProfileView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
addressrequired | string | |
clientPublicIdrequired | string | |
contactEmailrequired | string | |
contactNamerequired | string | |
netDays | integer or null | |
taxRateBasisPoints | integer or null | |
timeZone | string or null | |
usageMethod | string or null |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/billing-profile' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/billing-profile"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/billing-profile", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/billing-profile";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/billing-profile");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PUT /api/v1/clients/{cid}/billing-profile
Section titled “PUT /api/v1/clients/{cid}/billing-profile”PUTpsa:writemsp facemspIdempotent
Source: [object Object]
Put billing profile
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
addressrequired | string | |
contactEmailrequired | string | |
contactNamerequired | string | |
netDays | integer or null | |
taxRateBasisPoints | integer or null | |
timeZone | string or null | |
usageMethodrequired | any | Arbitrary JSON. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, ClientBillingProfileView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
addressrequired | string | |
clientPublicIdrequired | string | |
contactEmailrequired | string | |
contactNamerequired | string | |
netDays | integer or null | |
taxRateBasisPoints | integer or null | |
timeZone | string or null | |
usageMethod | string or null |
Request samples
Section titled “Request samples”# 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"}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "address": "string", "contactEmail": "string", "contactName": "string", "netDays": 0, "taxRateBasisPoints": 0, "timeZone": "string", "usageMethod": "string"}'@
$response = Invoke-RestMethod ` -Method Put ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/billing-profile' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/billing-profile"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "address": "string", "contactEmail": "string", "contactName": "string", "netDays": 0, "taxRateBasisPoints": 0, "timeZone": "string", "usageMethod": "string",}
response = requests.put(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "address": "string", "contactEmail": "string", "contactName": "string", "netDays": 0, "taxRateBasisPoints": 0, "timeZone": "string", "usageMethod": "string"}`)
req, err := http.NewRequest(http.MethodPut, "https://depot.example.com/api/v1/clients/<cid>/billing-profile", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/billing-profile";
const response = await fetch(url, { method: "PUT", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "address": "string", "contactEmail": "string", "contactName": "string", "netDays": 0, "taxRateBasisPoints": 0, "timeZone": "string", "usageMethod": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "address": "string", "contactEmail": "string", "contactName": "string", "netDays": 0, "taxRateBasisPoints": 0, "timeZone": "string", "usageMethod": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Put, "https://depot.example.com/api/v1/clients/<cid>/billing-profile");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
periodStartrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, InvoiceView | Created. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "periodStart": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/invoices/generate' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/invoices/generate"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "periodStart": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "periodStart": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/invoices/generate", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/invoices/generate";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "periodStart": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "periodStart": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/invoices/generate");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/clients/{cid}/ticket-types
Section titled “GET /api/v1/clients/{cid}/ticket-types”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, TicketTypeListView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of TicketTypeOptionView | |
items[].coderequired | string | |
items[].namerequired | string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/ticket-types' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/ticket-types"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/ticket-types", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/ticket-types";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/ticket-types");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes | |
entryID | path | string | Yes | A uuid. |
ticketID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, TicketTimeEntryView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
409 | application/problem+json, ProblemDetails | This client has no active agreement, so there are no rules to price this work against. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
amountCents | integer | int64. |
billablerequired | boolean | |
chargeTypeCode | string | |
classifiedAt | string (date-time) or null | |
invoicePublicId | string or null | |
invoicedrequired | boolean | |
locked | boolean | |
messagePublicId | string | |
minutesrequired | integer | |
noterequired | string | |
occurredAtrequired | string (date-time) | |
outcome | string | |
publicIdrequired | string | |
rateCentsSnapshot | integer or null | int64. |
technicianNamerequired | string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.post(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/clients/<cid>/tickets/<ticketID>/time/<entryID>/reprice");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/clients/{cid}/unbilled-time
Section titled “GET /api/v1/clients/{cid}/unbilled-time”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List client unbilled time
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes | |
outcome | query | string, one of covered, excluded, fixed, payg, prepaid, unclassified | No | Comma-separated set. An unrecognised value is refused. |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
project | query | string | No | |
subject | query | string | No |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultUnbilledTimeEntryView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of UnbilledTimeEntryView | |
items[].amountCentsrequired | integer | int64. |
items[].billablerequired | boolean | |
items[].chargeTypeCoderequired | string | |
items[].classifiedAt | string (date-time) or null | |
items[].clientNamerequired | string | |
items[].clientPublicIdrequired | string | |
items[].invoicePublicId | string or null | |
items[].noterequired | string | |
items[].outcome | string | |
items[].projectPublicIdrequired | string | |
items[].projectReferencerequired | string | |
items[].publicIdrequired | string | |
items[].rateCentsSnapshot | integer or null | int64. |
items[].secondsrequired | integer | |
items[].startedAtrequired | string (date-time) | |
items[].taskPublicIdrequired | string | |
items[].taskTitlerequired | string | |
items[].ticketPublicIdrequired | string | |
items[].ticketReferencerequired | string | |
items[].userPublicIdrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/unbilled-time?outcome=covered&page=0&pageSize=0&project=<project>&subject=<subject>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/unbilled-time"params = { "outcome": "covered", "page": "0", "pageSize": "0", "project": "<project>", "subject": "<subject>",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/unbilled-time?outcome=covered&page=0&pageSize=0&project=<project>&subject=<subject>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/clients/<cid>/unbilled-time");url.searchParams.set("outcome", "covered");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");url.searchParams.set("project", "<project>");url.searchParams.set("subject", "<subject>");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/unbilled-time?outcome=covered&page=0&pageSize=0&project=<project>&subject=<subject>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/clients/{cid}/usage
Section titled “GET /api/v1/clients/{cid}/usage”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
cid | path | string | Yes | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultUsageDayView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No client matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of UsageDayView | |
items[].carriedrequired | boolean | |
items[].daterequired | string | |
items[].endpointCountrequired | integer | |
items[].endpointsrequired | array of UsageSnapshotEndpoint | |
items[].endpoints[].classrequired | string | |
items[].endpoints[].groupsrequired | array of string | |
items[].endpoints[].idrequired | string | |
items[].endpoints[].osrequired | string | |
items[].endpoints[].osDisplayrequired | string | |
items[].endpoints[].rolesrequired | array of string | |
items[].endpoints[].siterequired | string | |
items[].takenAtrequired | string (date-time) | |
items[].userCountrequired | integer | |
items[].usersrequired | array of UsageSnapshotUser | |
items[].users[].endpointIdsrequired | array of string | |
items[].users[].idrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/clients/<cid>/usage?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/clients/<cid>/usage"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/clients/<cid>/usage?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/clients/<cid>/usage");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/clients/<cid>/usage?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/agreements
Section titled “GET /api/v1/psa/agreements”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List all agreements
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
client | query | string | No | |
clientId | query | string | No | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
status | query | string, one of active, draft, ended | No | Comma-separated set. An unrecognised value is refused. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PsaFanOutPagePracticeAgreementView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
500 | application/problem+json, ProblemDetails | Could not list clients. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of PracticeAgreementView | |
items[].billingDayrequired | integer | |
items[].clientNamerequired | string | |
items[].clientPublicIdrequired | string | |
items[].defaultChargeTypeCode | string or null | |
items[].defaultRateCentsrequired | integer | int64. |
items[].endsOn | string or null | |
items[].namerequired | string | |
items[].prepaidBalanceMinutes | integer or null | |
items[].publicIdrequired | string | |
items[].recurringCentsrequired | integer | int64. |
items[].startsOnrequired | string | |
items[].statusrequired | string | |
items[].templatePublicId | string or null | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer | |
totalClientsrequired | integer | |
unavailableClientsrequired | array of string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/agreements?client=<client>&clientId=<clientId>&page=0&pageSize=0&status=active' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/agreements"params = { "client": "<client>", "clientId": "<clientId>", "page": "0", "pageSize": "0", "status": "active",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/agreements?client=<client>&clientId=<clientId>&page=0&pageSize=0&status=active", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/agreements");url.searchParams.set("client", "<client>");url.searchParams.set("clientId", "<clientId>");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");url.searchParams.set("status", "active");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/agreements?client=<client>&clientId=<clientId>&page=0&pageSize=0&status=active");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/ar-aging
Section titled “GET /api/v1/psa/ar-aging”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.
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, ArAgingView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
asOfrequired | string | |
currencyCoderequired | string | |
rowsrequired | array of ArAgingRowView | |
rows[].clientNamerequired | string | |
rows[].clientPublicIdrequired | string | |
rows[].currentCentsrequired | integer | int64. |
rows[].days30Centsrequired | integer | int64. |
rows[].days60Centsrequired | integer | int64. |
rows[].days90Centsrequired | integer | int64. |
rows[].over90Centsrequired | integer | int64. |
rows[].totalCentsrequired | integer | int64. |
totalsrequired | ArAgingTotalsView | 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.currentCentsrequired | integer | int64. |
totals.days30Centsrequired | integer | int64. |
totals.days60Centsrequired | integer | int64. |
totals.days90Centsrequired | integer | int64. |
totals.over90Centsrequired | integer | int64. |
totals.totalCentsrequired | integer | int64. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/ar-aging' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/ar-aging"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/ar-aging", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/ar-aging";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/ar-aging");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/charge-types
Section titled “GET /api/v1/psa/charge-types”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List charge types
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultChargeTypeView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of ChargeTypeView | |
items[].activerequired | boolean | |
items[].coderequired | string | |
items[].defaultRateCentsrequired | integer | int64. |
items[].namerequired | string | |
items[].publicIdrequired | string | |
items[].sortOrderrequired | integer | |
items[].unitrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/charge-types?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/charge-types"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/charge-types?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/charge-types");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/charge-types?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/psa/charge-types
Section titled “POST /api/v1/psa/charge-types”POSTpsa:adminmsp facemsp
Source: [object Object]
Create charge type
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
coderequired | string | |
defaultRateCentsrequired | integer | int64. |
namerequired | string | |
sortOrder | integer or null | |
unitrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, ChargeTypeView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
activerequired | boolean | |
coderequired | string | |
defaultRateCentsrequired | integer | int64. |
namerequired | string | |
publicIdrequired | string | |
sortOrderrequired | integer | |
unitrequired | string |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "code": "string", "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/charge-types' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/charge-types"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "code": "string", "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "code": "string", "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/charge-types", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/charge-types";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "code": "string", "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "code": "string", "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/charge-types");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PATCH /api/v1/psa/charge-types/{code}
Section titled “PATCH /api/v1/psa/charge-types/{code}”PATCHpsa:adminmsp facemsp
Source: [object Object]
Patch charge type
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
code | path | string | Yes |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
active | boolean or null | |
defaultRateCents | integer or null | int64. |
name | string or null | |
sortOrder | integer or null | |
unit | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, ChargeTypeView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No charge type matches that code. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
activerequired | boolean | |
coderequired | string | |
defaultRateCentsrequired | integer | int64. |
namerequired | string | |
publicIdrequired | string | |
sortOrderrequired | integer | |
unitrequired | string |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "active": false, "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/psa/charge-types/<code>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/charge-types/<code>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "active": False, "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string",}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "active": false, "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/psa/charge-types/<code>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/charge-types/<code>";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "active": false, "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "active": false, "defaultRateCents": 0, "name": "string", "sortOrder": 0, "unit": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/psa/charge-types/<code>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/psa/charge-types/{code}
Section titled “DELETE /api/v1/psa/charge-types/{code}”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
code | path | string | Yes |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/psa/charge-types/<code>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/charge-types/<code>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.delete(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/psa/charge-types/<code>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/charge-types/<code>";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/psa/charge-types/<code>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/export.csv
Section titled “GET /api/v1/psa/export.csv”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
format | query | string | No |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/octet-stream, string (binary) | Success. The body shape was not resolved; see x-outpost-unresolved. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/export.csv?format=<format>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/export.csv"params = { "format": "<format>",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/export.csv?format=<format>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/export.csv");url.searchParams.set("format", "<format>");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/export.csv?format=<format>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/invoices
Section titled “GET /api/v1/psa/invoices”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List invoices
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
client | query | string | No | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
q | query | string | No | Free-text search term. |
sort | query | string, one of balance, due, issued, number, total | No | Sort key. |
status | query | string, one of draft, issued, paid, partial, past_due, sent, void | No | Comma-separated set. An unrecognised value is refused. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultInvoiceSummaryView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of InvoiceSummaryView | |
items[].agreementPublicId | string or null | |
items[].balanceCentsrequired | integer | int64. |
items[].clientNamerequired | string | |
items[].clientPublicIdrequired | string | |
items[].creditedCentsrequired | integer | int64. |
items[].currencyCoderequired | string | |
items[].dueDate | string or null | |
items[].invoiceNumber | string or null | |
items[].issueDate | string or null | |
items[].paidCentsrequired | integer | int64. |
items[].periodEnd | string or null | |
items[].periodStart | string or null | |
items[].projectPublicId | string or null | |
items[].publicIdrequired | string | |
items[].statusrequired | string | |
items[].subtotalCentsrequired | integer | int64. |
items[].taxCentsrequired | integer | int64. |
items[].taxRateBasisPointsrequired | integer | |
items[].totalCentsrequired | integer | int64. |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/invoices?client=<client>&page=0&pageSize=0&q=<q>&sort=balance&status=draft' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices"params = { "client": "<client>", "page": "0", "pageSize": "0", "q": "<q>", "sort": "balance", "status": "draft",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/invoices?client=<client>&page=0&pageSize=0&q=<q>&sort=balance&status=draft", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/invoices");url.searchParams.set("client", "<client>");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");url.searchParams.set("q", "<q>");url.searchParams.set("sort", "balance");url.searchParams.set("status", "draft");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/invoices?client=<client>&page=0&pageSize=0&q=<q>&sort=balance&status=draft");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/psa/invoices
Section titled “POST /api/v1/psa/invoices”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.
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
clientIdrequired | string | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
taxRateBasisPoints | integer or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, InvoiceView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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,}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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 }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/invoices/{invoiceID}
Section titled “GET /api/v1/psa/invoices/{invoiceID}”GETpsa:readmsp facemspIdempotent
Source: [object Object]
Get invoice
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, InvoiceView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PATCH /api/v1/psa/invoices/{invoiceID}
Section titled “PATCH /api/v1/psa/invoices/{invoiceID}”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”.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
lines | array of InvoiceLineView or null | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notes | string or null | |
taxRateBasisPoints | integer or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, InvoiceView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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,}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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 }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/psa/invoices/{invoiceID}
Section titled “DELETE /api/v1/psa/invoices/{invoiceID}”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:write. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.delete(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
amountCentsrequired | integer | int64. |
kindrequired | string | |
reasonrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, CreditNoteView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
creditNoteNumberrequired | string | |
issueDaterequired | string | |
kindrequired | string | |
publicIdrequired | string | |
reasonrequired | string | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
totalCentsrequired | integer | int64. |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "amountCents": 0, "kind": "string", "reason": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "amountCents": 0, "kind": "string", "reason": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "amountCents": 0, "kind": "string", "reason": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "amountCents": 0, "kind": "string", "reason": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "amountCents": 0, "kind": "string", "reason": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/credit-notes");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, InvoiceView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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>'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.post(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/finalize");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/invoices/{invoiceID}/payments
Section titled “GET /api/v1/psa/invoices/{invoiceID}/payments”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List payments
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultPaymentView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of PaymentView | |
items[].amountCentsrequired | integer | int64. |
items[].methodrequired | string | |
items[].noterequired | string | |
items[].publicIdrequired | string | |
items[].receivedOnrequired | string | |
items[].referencerequired | string | |
items[].voidedAt | string (date-time) or null | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
amountCentsrequired | integer | int64. |
methodrequired | string | |
noterequired | string | |
receivedOnrequired | string | |
referencerequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, PaymentView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
amountCentsrequired | integer | int64. |
methodrequired | string | |
noterequired | string | |
publicIdrequired | string | |
receivedOnrequired | string | |
referencerequired | string | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "amountCents": 0, "method": "string", "note": "string", "receivedOn": "string", "reference": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "amountCents": 0, "method": "string", "note": "string", "receivedOn": "string", "reference": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "amountCents": 0, "method": "string", "note": "string", "receivedOn": "string", "reference": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "amountCents": 0, "method": "string", "note": "string", "receivedOn": "string", "reference": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "amountCents": 0, "method": "string", "note": "string", "receivedOn": "string", "reference": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/payments");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/invoices/{invoiceID}/pdf
Section titled “GET /api/v1/psa/invoices/{invoiceID}/pdf”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/octet-stream, string (binary) | Success. The body shape was not resolved; see x-outpost-unresolved. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/pdf");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
torequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, InvoiceView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
409 | application/problem+json, ProblemDetails | A draft has no number and no document. Finalize it first. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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"}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "to": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "to": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "to": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "to": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "to": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/send");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
invoiceID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
reasonrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, InvoiceView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:invoice. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
agreementPublicId | string or null | |
balanceCentsrequired | integer | int64. |
clientNamerequired | string | |
clientPublicIdrequired | string | |
creditNotesrequired | array of CreditNoteView | |
creditNotes[].creditNoteNumberrequired | string | |
creditNotes[].issueDaterequired | string | |
creditNotes[].kindrequired | string | |
creditNotes[].publicIdrequired | string | |
creditNotes[].reasonrequired | string | |
creditNotes[].statusrequired | string | |
creditNotes[].subtotalCentsrequired | integer | int64. |
creditNotes[].taxCentsrequired | integer | int64. |
creditNotes[].totalCentsrequired | integer | int64. |
creditedCentsrequired | integer | int64. |
currencyCoderequired | string | |
dueDate | string or null | |
finalizedAt | string (date-time) or null | |
invoiceNumber | string or null | |
issueDate | string or null | |
linesrequired | array of InvoiceLineView | |
lines[].agreementLinePublicId | string or null | |
lines[].amountCentsrequired | integer | int64. |
lines[].changeOrderPublicId | string or null | |
lines[].descriptionrequired | string | |
lines[].kindrequired | string | |
lines[].milestonePublicId | string or null | |
lines[].projectPublicId | string or null | |
lines[].publicId | string | |
lines[].quantityrequired | string | |
lines[].quantitySnapshot | QuantitySnapshotView | |
lines[].quantitySnapshot.countrequired | integer | |
lines[].quantitySnapshot.idsrequired | array of string | |
lines[].sortOrderrequired | integer | |
lines[].timeEntryPublicId | string or null | |
lines[].unitAmountCentsrequired | integer | int64. |
notesrequired | string | |
paidCentsrequired | integer | int64. |
paymentsrequired | array of PaymentView | |
payments[].amountCentsrequired | integer | int64. |
payments[].methodrequired | string | |
payments[].noterequired | string | |
payments[].publicIdrequired | string | |
payments[].receivedOnrequired | string | |
payments[].referencerequired | string | |
payments[].voidedAt | string (date-time) or null | |
periodEnd | string or null | |
periodStart | string or null | |
projectPublicId | string or null | |
publicIdrequired | string | |
remitTorequired | string | |
sentAt | string (date-time) or null | |
statusrequired | string | |
subtotalCentsrequired | integer | int64. |
taxCentsrequired | integer | int64. |
taxRateBasisPointsrequired | integer | |
totalCentsrequired | integer | int64. |
voidReason | string or null | |
voidedAt | string (date-time) or null |
Request samples
Section titled “Request samples”# 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"}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "reason": "string"}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "reason": "string",}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "reason": "string"}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "reason": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "reason": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/invoices/<invoiceID>/void");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/ready-to-invoice
Section titled “GET /api/v1/psa/ready-to-invoice”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
client | query | string | No | |
clientId | query | string | No | |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PsaFanOutPageReadyToInvoiceClientView | Success. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
500 | application/problem+json, ProblemDetails | Could not list clients. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of ReadyToInvoiceClientView | |
items[].agreementPublicIdrequired | string | |
items[].clientNamerequired | string | |
items[].clientPublicIdrequired | string | |
items[].draftInvoicePublicId | string or null | |
items[].entryCountrequired | integer | |
items[].lineCountrequired | integer | |
items[].periodEndrequired | string | |
items[].periodStartrequired | string | |
items[].recurringCentsrequired | integer | int64. |
items[].timeCentsrequired | integer | int64. |
items[].totalCentsrequired | integer | int64. |
items[].unbilledMinutesrequired | integer | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer | |
totalClientsrequired | integer | |
unavailableClientsrequired | array of string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/ready-to-invoice?client=<client>&clientId=<clientId>&page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/ready-to-invoice"params = { "client": "<client>", "clientId": "<clientId>", "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/ready-to-invoice?client=<client>&clientId=<clientId>&page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/ready-to-invoice");url.searchParams.set("client", "<client>");url.searchParams.set("clientId", "<clientId>");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/ready-to-invoice?client=<client>&clientId=<clientId>&page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/settings
Section titled “GET /api/v1/psa/settings”GETpsa:readmsp facemspIdempotent
Source: [object Object]
Get settings
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, BillingSettingsView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
currencyCoderequired | string | |
invoicePrefixrequired | string | |
netDaysrequired | integer | |
overdueReminderEveryDaysrequired | integer | |
prepaidLowThresholdMinutesrequired | integer | |
reminderDaysBeforerequired | integer | |
remitTorequired | string | |
taxRateBasisPointsrequired | integer | |
usageMethodrequired | string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/settings' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/settings"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/settings", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/settings";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/settings");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PUT /api/v1/psa/settings
Section titled “PUT /api/v1/psa/settings”PUTpsa:adminmsp facemspIdempotent
Source: [object Object]
Put settings
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
currencyCoderequired | string | |
invoicePrefixrequired | string | |
netDaysrequired | integer | |
overdueReminderEveryDaysrequired | integer | |
prepaidLowThresholdMinutesrequired | integer | |
reminderDaysBeforerequired | integer | |
remitTorequired | string | |
taxRateBasisPointsrequired | integer | |
usageMethodrequired | string |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, BillingSettingsView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
currencyCoderequired | string | |
invoicePrefixrequired | string | |
netDaysrequired | integer | |
overdueReminderEveryDaysrequired | integer | |
prepaidLowThresholdMinutesrequired | integer | |
reminderDaysBeforerequired | integer | |
remitTorequired | string | |
taxRateBasisPointsrequired | integer | |
usageMethodrequired | string |
Request samples
Section titled “Request samples”# 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"}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "currencyCode": "string", "invoicePrefix": "string", "netDays": 0, "overdueReminderEveryDays": 0, "prepaidLowThresholdMinutes": 0, "reminderDaysBefore": 0, "remitTo": "string", "taxRateBasisPoints": 0, "usageMethod": "string"}'@
$response = Invoke-RestMethod ` -Method Put ` -Uri 'https://depot.example.com/api/v1/psa/settings' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/settings"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "currencyCode": "string", "invoicePrefix": "string", "netDays": 0, "overdueReminderEveryDays": 0, "prepaidLowThresholdMinutes": 0, "reminderDaysBefore": 0, "remitTo": "string", "taxRateBasisPoints": 0, "usageMethod": "string",}
response = requests.put(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "currencyCode": "string", "invoicePrefix": "string", "netDays": 0, "overdueReminderEveryDays": 0, "prepaidLowThresholdMinutes": 0, "reminderDaysBefore": 0, "remitTo": "string", "taxRateBasisPoints": 0, "usageMethod": "string"}`)
req, err := http.NewRequest(http.MethodPut, "https://depot.example.com/api/v1/psa/settings", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/settings";
const response = await fetch(url, { method: "PUT", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "currencyCode": "string", "invoicePrefix": "string", "netDays": 0, "overdueReminderEveryDays": 0, "prepaidLowThresholdMinutes": 0, "reminderDaysBefore": 0, "remitTo": "string", "taxRateBasisPoints": 0, "usageMethod": "string" }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "currencyCode": "string", "invoicePrefix": "string", "netDays": 0, "overdueReminderEveryDays": 0, "prepaidLowThresholdMinutes": 0, "reminderDaysBefore": 0, "remitTo": "string", "taxRateBasisPoints": 0, "usageMethod": "string"}""";
var request = new HttpRequestMessage(HttpMethod.Put, "https://depot.example.com/api/v1/psa/settings");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/templates
Section titled “GET /api/v1/psa/templates”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List templates
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PagedResultAgreementTemplateView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of AgreementTemplateView | |
items[].billingDayrequired | integer | |
items[].defaultChargeTypeCode | string or null | |
items[].defaultRateCentsrequired | integer | int64. |
items[].descriptionrequired | string | |
items[].linesrequired | array of AgreementTemplateLineView | |
items[].lines[].descriptionrequired | string | |
items[].lines[].exclusive | boolean or null | |
items[].lines[].filter | any | Arbitrary JSON. |
items[].lines[].includedDevicesPerSeat | integer or null | |
items[].lines[].includedMinutesrequired | integer | |
items[].lines[].kindrequired | string | |
items[].lines[].overageUnitAmountCents | integer or null | int64. |
items[].lines[].publicId | string | |
items[].lines[].seatRequiresEndpoint | boolean or null | |
items[].lines[].sortOrderrequired | integer | |
items[].lines[].unitAmountCentsrequired | integer | int64. |
items[].lines[].usageMethod | any | Arbitrary JSON. |
items[].namerequired | string | |
items[].publicIdrequired | string | |
items[].rulesrequired | array of AgreementTemplateRuleView | |
items[].rules[].chargeTypeCode | string or null | |
items[].rules[].descriptionrequired | string | |
items[].rules[].outOfHours | boolean or null | |
items[].rules[].outcomerequired | string | |
items[].rules[].positionrequired | integer | |
items[].rules[].prepaidTemplateLinePublicId | string or null | |
items[].rules[].projectWork | boolean or null | |
items[].rules[].publicId | string | |
items[].rules[].rateCents | integer or null | int64. |
items[].rules[].ticketTypeCode | string or null | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/templates?page=0&pageSize=0' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/templates"params = { "page": "0", "pageSize": "0",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/templates?page=0&pageSize=0", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/templates");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/templates?page=0&pageSize=0");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());POST /api/v1/psa/templates
Section titled “POST /api/v1/psa/templates”POSTpsa:adminmsp facemsp
Source: [object Object]
Create template
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
billingDay | integer or null | |
defaultChargeTypeCoderequired | any | Arbitrary JSON. |
defaultRateCents | integer or null | int64. |
description | string or null | |
linesrequired | array of AgreementTemplateLineView | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
name | string or null | |
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
201 | application/json, AgreementTemplateView | Created. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
201 response body
| Field | Type | Description |
|---|---|---|
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
descriptionrequired | string | |
linesrequired | array of AgreementTemplateLineView | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
publicIdrequired | string | |
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Request samples
Section titled “Request samples”# 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" } ]}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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" } ]}'@
$response = Invoke-RestMethod ` -Method Post ` -Uri 'https://depot.example.com/api/v1/psa/templates' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/templates"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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", }, ],}
response = requests.post(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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" } ]}`)
req, err := http.NewRequest(http.MethodPost, "https://depot.example.com/api/v1/psa/templates", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/templates";
const response = await fetch(url, { method: "POST", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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" } ] }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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" } ]}""";
var request = new HttpRequestMessage(HttpMethod.Post, "https://depot.example.com/api/v1/psa/templates");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/templates/{templateID}
Section titled “GET /api/v1/psa/templates/{templateID}”GETpsa:readmsp facemspIdempotent
Source: [object Object]
Get template
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
templateID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementTemplateView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
descriptionrequired | string | |
linesrequired | array of AgreementTemplateLineView | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
publicIdrequired | string | |
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/templates/<templateID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/templates/<templateID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/templates/<templateID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/templates/<templateID>";
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/templates/<templateID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());PATCH /api/v1/psa/templates/{templateID}
Section titled “PATCH /api/v1/psa/templates/{templateID}”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”.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
templateID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
billingDay | integer or null | |
defaultChargeTypeCoderequired | any | Arbitrary JSON. |
defaultRateCents | integer or null | int64. |
description | string or null | |
linesrequired | array of AgreementTemplateLineView | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
name | string or null | |
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementTemplateView | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
billingDayrequired | integer | |
defaultChargeTypeCode | string or null | |
defaultRateCentsrequired | integer | int64. |
descriptionrequired | string | |
linesrequired | array of AgreementTemplateLineView | |
lines[].descriptionrequired | string | |
lines[].exclusive | boolean or null | |
lines[].filter | any | Arbitrary JSON. |
lines[].includedDevicesPerSeat | integer or null | |
lines[].includedMinutesrequired | integer | |
lines[].kindrequired | string | |
lines[].overageUnitAmountCents | integer or null | int64. |
lines[].publicId | string | |
lines[].seatRequiresEndpoint | boolean or null | |
lines[].sortOrderrequired | integer | |
lines[].unitAmountCentsrequired | integer | int64. |
lines[].usageMethod | any | Arbitrary JSON. |
namerequired | string | |
publicIdrequired | string | |
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Request samples
Section titled “Request samples”# 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" } ]}'# Sign in first. outpost_console is a session cookie, so send it with every request.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "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" } ]}'@
$response = Invoke-RestMethod ` -Method Patch ` -Uri 'https://depot.example.com/api/v1/psa/templates/<templateID>' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# Sign in first. outpost_console is a session cookie, so send it with every request.
import requests
url = "https://depot.example.com/api/v1/psa/templates/<templateID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "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", }, ],}
response = requests.patch(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// Sign in first. outpost_console is a session cookie, so send it with every request.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "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" } ]}`)
req, err := http.NewRequest(http.MethodPatch, "https://depot.example.com/api/v1/psa/templates/<templateID>", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// Sign in first. outpost_console is a session cookie, so send it with every request.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/templates/<templateID>";
const response = await fetch(url, { method: "PATCH", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "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" } ] }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// Sign in first. outpost_console is a session cookie, so send it with every request.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "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" } ]}""";
var request = new HttpRequestMessage(HttpMethod.Patch, "https://depot.example.com/api/v1/psa/templates/<templateID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());DELETE /api/v1/psa/templates/{templateID}
Section titled “DELETE /api/v1/psa/templates/{templateID}”DELETEpsa:adminmsp facemspIdempotent
Source: [object Object]
Delete template
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
templateID | path | string | Yes | A uuid. |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
204 | None | Success, with no body. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Delete ` -Uri 'https://depot.example.com/api/v1/psa/templates/<templateID>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/templates/<templateID>"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.delete(url, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodDelete, "https://depot.example.com/api/v1/psa/templates/<templateID>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/templates/<templateID>";
const response = await fetch(url, { method: "DELETE", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://depot.example.com/api/v1/psa/templates/<templateID>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
templateID | path | string | Yes | A uuid. |
Request body
Section titled “Request body”Required, sent as application/json.
| Field | Type | Description |
|---|---|---|
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, AgreementTemplateRulesRequest | Success. |
400 | application/problem+json, ProblemDetails | The request body is not valid JSON. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:admin. |
404 | application/problem+json, ProblemDetails | No resource matches that id. |
500 | application/problem+json, ProblemDetails | The request failed. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
rulesrequired | array of AgreementTemplateRuleView | |
rules[].chargeTypeCode | string or null | |
rules[].descriptionrequired | string | |
rules[].outOfHours | boolean or null | |
rules[].outcomerequired | string | |
rules[].positionrequired | integer | |
rules[].prepaidTemplateLinePublicId | string or null | |
rules[].projectWork | boolean or null | |
rules[].publicId | string | |
rules[].rateCents | integer or null | int64. |
rules[].ticketTypeCode | string or null |
Request samples
Section titled “Request samples”# 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" } ]}'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Content-Type' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$body = @'{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidTemplateLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}'@
$response = Invoke-RestMethod ` -Method Put ` -Uri 'https://depot.example.com/api/v1/psa/templates/<templateID>/rules' ` -Headers $headers ` -ContentType 'application/json' ` -Body $body
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/templates/<templateID>/rules"headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json",}cookies = { "outpost_console": "<console-session>",}payload = { "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": False, "outcome": "string", "position": 0, "prepaidTemplateLinePublicId": "string", "projectWork": False, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string", }, ],}
response = requests.put(url, headers=headers, cookies=cookies, json=payload, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "bytes" "fmt" "io" "log" "net/http")
func main() { body := []byte(`{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidTemplateLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}`)
req, err := http.NewRequest(http.MethodPut, "https://depot.example.com/api/v1/psa/templates/<templateID>/rules", bytes.NewReader(body)) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = "https://depot.example.com/api/v1/psa/templates/<templateID>/rules";
const response = await fetch(url, { method: "PUT", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidTemplateLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ] }),});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;using System.Text;
using var client = new HttpClient();
const string body = """{ "rules": [ { "chargeTypeCode": "string", "description": "string", "outOfHours": false, "outcome": "string", "position": 0, "prepaidTemplateLinePublicId": "string", "projectWork": false, "publicId": "string", "rateCents": 0, "ticketTypeCode": "string" } ]}""";
var request = new HttpRequestMessage(HttpMethod.Put, "https://depot.example.com/api/v1/psa/templates/<templateID>/rules");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());GET /api/v1/psa/unbilled-time
Section titled “GET /api/v1/psa/unbilled-time”GETpsa:readmsp facemspIdempotent
Source: [object Object]
List unbilled time
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
client | query | string | No | |
clientId | query | string | No | |
outcome | query | string, one of covered, excluded, fixed, payg, prepaid, unclassified | No | Comma-separated set. An unrecognised value is refused. |
page | query | integer | No | One-based page number. Defaults to 1. |
pageSize | query | integer | No | Rows per page, 1 to 200. Defaults to 50. |
project | query | string | No | |
subject | query | string | No |
Responses
Section titled “Responses”| Status | Body | Description |
|---|---|---|
200 | application/json, PsaFanOutPageUnbilledTimeEntryView | Success. |
400 | application/problem+json, ProblemDetails | One or more validation errors occurred. |
401 | application/problem+json, ProblemDetails | No signed-in identity. |
403 | application/problem+json, ProblemDetails | The identity does not hold psa:read. |
500 | application/problem+json, ProblemDetails | Could not list clients. |
503 | application/problem+json, ProblemDetails | Client billing is not available on this server. |
200 response body
| Field | Type | Description |
|---|---|---|
itemsrequired | array of UnbilledTimeEntryView | |
items[].amountCentsrequired | integer | int64. |
items[].billablerequired | boolean | |
items[].chargeTypeCoderequired | string | |
items[].classifiedAt | string (date-time) or null | |
items[].clientNamerequired | string | |
items[].clientPublicIdrequired | string | |
items[].invoicePublicId | string or null | |
items[].noterequired | string | |
items[].outcome | string | |
items[].projectPublicIdrequired | string | |
items[].projectReferencerequired | string | |
items[].publicIdrequired | string | |
items[].rateCentsSnapshot | integer or null | int64. |
items[].secondsrequired | integer | |
items[].startedAtrequired | string (date-time) | |
items[].taskPublicIdrequired | string | |
items[].taskTitlerequired | string | |
items[].ticketPublicIdrequired | string | |
items[].ticketReferencerequired | string | |
items[].userPublicIdrequired | string | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer | |
totalClientsrequired | integer | |
unavailableClientsrequired | array of string |
Request samples
Section titled “Request samples”# 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>'# 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.
$headers = @{ 'X-CSRF-Token' = '<csrf-token>' 'Accept' = 'application/json' 'Cookie' = 'outpost_console=<console-session>'}
$response = Invoke-RestMethod ` -Method Get ` -Uri 'https://depot.example.com/api/v1/psa/unbilled-time?client=<client>&clientId=<clientId>&outcome=covered&page=0&pageSize=0&project=<project>' ` -Headers $headers
$response | ConvertTo-Json -Depth 10# 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.
import requests
url = "https://depot.example.com/api/v1/psa/unbilled-time"params = { "client": "<client>", "clientId": "<clientId>", "outcome": "covered", "page": "0", "pageSize": "0", "project": "<project>",}headers = { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json",}cookies = { "outpost_console": "<console-session>",}
response = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=30)response.raise_for_status()print(response.json())// 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.package main
import ( "fmt" "io" "log" "net/http")
func main() { req, err := http.NewRequest(http.MethodGet, "https://depot.example.com/api/v1/psa/unbilled-time?client=<client>&clientId=<clientId>&outcome=covered&page=0&pageSize=0&project=<project>", nil) if err != nil { log.Fatal(err) }
req.Header.Set("X-CSRF-Token", "<csrf-token>") req.Header.Set("Accept", "application/json") req.AddCookie(&http.Cookie{Name: "outpost_console", Value: "<console-session>"})
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
out, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(out))}// 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.// The session cookie is set by signing in. A script cannot set it itself.
const url = new URL("https://depot.example.com/api/v1/psa/unbilled-time");url.searchParams.set("client", "<client>");url.searchParams.set("clientId", "<clientId>");url.searchParams.set("outcome", "covered");url.searchParams.set("page", "0");url.searchParams.set("pageSize", "0");url.searchParams.set("project", "<project>");
const response = await fetch(url, { method: "GET", headers: { "X-CSRF-Token": "<csrf-token>", "Accept": "application/json", }, credentials: "include",});
if (!response.ok) { throw new Error(`Request failed with status ${response.status}`);}
const data = await response.json();console.log(data);// 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.
using System;using System.Net.Http;
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://depot.example.com/api/v1/psa/unbilled-time?client=<client>&clientId=<clientId>&outcome=covered&page=0&pageSize=0&project=<project>");request.Headers.TryAddWithoutValidation("X-CSRF-Token", "<csrf-token>");request.Headers.TryAddWithoutValidation("Accept", "application/json");request.Headers.TryAddWithoutValidation("Cookie", "outpost_console=<console-session>");
var response = await client.SendAsync(request);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());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.