Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/spec/packages/aip/src/accounting/index.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./period.tsp";
import "./operations.tsp";
162 changes: 162 additions & 0 deletions api/spec/packages/aip/src/accounting/operations.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi";
import "@typespec/openapi3";
import "../common/error.tsp";
import "../common/pagination.tsp";
import "../common/parameters.tsp";
import "../common/properties.tsp";
import "../shared/index.tsp";
import "./period.tsp";

using TypeSpec.Http;
using TypeSpec.OpenAPI;

namespace Accounting;

/**
* Filter options for listing accounting periods.
*/
@friendlyName("ListAccountingPeriodsParamsFilter")
model ListAccountingPeriodsParamsFilter {
/**
* Filter periods by lifecycle status. Use `filter[status][oeq]=open,closing` to
* fetch the currently-active periods.
*/
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
status?: Common.StringFieldFilterExact;

/**
* Filter periods by `start_at`.
*/
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
start_at?: Common.DateTimeFieldFilter;

/**
* Filter periods by `end_at`. Open periods (no `end_at`) are excluded when this
* filter is applied.
*/
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
end_at?: Common.DateTimeFieldFilter;

/**
* Filter periods by labels.
*/
#suppress "@openmeter/api-spec-aip/doc-decorator" "filter field"
labels?: Common.LabelsFieldFilter;
}

/**
* Request body for initiating a close on the currently-open accounting period.
*/
@friendlyName("CloseAccountingPeriodRequest")
@summary("Close accounting period request")
model CloseAccountingPeriodRequest {
/**
* The intended end of the period being closed (exclusive). Must be strictly
* greater than the open period's `start_at` and not in the future.
*
* Becomes the closing period's `end_at` and the newly-opened next period's
* `start_at`.
*/
@summary("End of period")
end_at: Shared.DateTime;

/**
* Optional labels applied to the closing period.
*/
@summary("Labels")
labels?: Common.Labels;

/**
* Optional display name for the period (e.g. "2026-Q2"). Auto-generated if
* omitted.
*/
@summary("Name")
@minLength(1)
@maxLength(256)
name?: string;

/**
* Optional description.
*/
@summary("Description")
@maxLength(1024)
description?: string;
}

interface AccountingPeriodsOperations {
/**
* Close the currently-open accounting period.
*
* Atomically, under a namespace-period advisory lock:
*
* 1. Freezes membership on the currently-open period `P`: sets `P.end_at`,
* transitions `P.status` to `closing`, and records `P.closing_started_at`.
* 2. Opens a new period `N` with `N.start_at = P.end_at` and `N.status = open`.
*
* The transition `closing → closed` (balance snapshot computation, close-time
* validations, and seal hash commit) is performed by the close pipeline; poll
* `GET .../{periodId}` to observe the final `closed` state, `closed_at`,
* `sealed_at`, and seal fields.
*
* Late-arriving transactions whose `booked_at` falls inside the now-closed period
* are posted to `N` with an `adjusts_period_id` backpointer to `P`; they do not
* modify `P`.
*
* Returns the period transitioned to `closing`. The newly-opened next period can
* be retrieved by listing with `filter[status][eq]=open`.
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
@extension(Shared.UnstableExtension, true)
@extension(Shared.InternalExtension, true)
@post
@route("/close")
@operationId("close-accounting-period")
@summary("Close the open accounting period")
close(
@body body: CloseAccountingPeriodRequest,
): Shared.UpdateResponse<AccountingPeriod> | Common.ErrorResponses;

Comment thread
coderabbitai[bot] marked this conversation as resolved.
/**
* Get an accounting period by id.
*/
@extension(Shared.UnstableExtension, true)
@extension(Shared.InternalExtension, true)
@get
@operationId("get-accounting-period")
@summary("Get an accounting period")
get(
@path periodId: Shared.ULID,
): Shared.GetResponse<AccountingPeriod> | Common.NotFound | Common.ErrorResponses;

/**
* List accounting periods.
*
* Returns the namespace's periods in reverse-chronological order by `start_at` by
* default (most recent first).
*/
@extension(Shared.UnstableExtension, true)
@extension(Shared.InternalExtension, true)
@get
@operationId("list-accounting-periods")
@summary("List accounting periods")
list(
...Common.PagePaginationQuery,

/**
* Sort periods returned in the response. Supported sort attributes are:
*
* - `start_at` (default, descending)
* - `created_at`
* - `id`
*/
@query(#{ name: "sort" })
sort?: Common.SortQuery,

/**
* Filter periods returned in the response.
*/
@query(#{ style: "deepObject", explode: true })
filter?: ListAccountingPeriodsParamsFilter,
): Shared.PagePaginatedResponse<AccountingPeriod> | Common.ErrorResponses;
}
121 changes: 121 additions & 0 deletions api/spec/packages/aip/src/accounting/period.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import "../shared/index.tsp";

namespace Accounting;

/**
* Lifecycle status of an accounting period.
*
* - `open`: the current posting bucket. New transactions can be posted to this
* period. Exactly one period per namespace is `open` at any time.
* - `closing`: membership is frozen — no new transactions can post to this period
* — but balance snapshots and the seal have not yet been committed. At most one
* period per namespace is `closing` at any time.
* - `closed`: the period is sealed. `closed_at`, `sealed_at`, and (when sealing is
* enabled) `seal_hash` are set. Transaction membership and the closing balance
* snapshots are immutable.
*/
@friendlyName("AccountingPeriodStatus")
@summary("Accounting period status")
enum AccountingPeriodStatus {
// The current posting bucket for new transactions.
Open: "open",

// Membership is frozen but the seal has not yet been committed.
Closing: "closing",

// The period is sealed and immutable.
Closed: "closed",
}

/**
* An accounting period is a sequential, non-overlapping window of ledger activity
* within a namespace. Closing a period freezes its transaction membership and
* (eventually) seals an authoritative closing balance.
*
* Late-arriving transactions whose `booked_at` falls inside an already-closed
* period are posted to the current open period with an `adjusts_period_id`
* backpointer; they do not mutate the closed period.
*/
@friendlyName("AccountingPeriod")
@summary("Accounting period")
model AccountingPeriod {
...Shared.ResourceImmutable;

/**
* Start of the period (inclusive).
*
* For the first period in a namespace this is the namespace creation time. For
* every subsequent period, equals the previous period's `end_at`. Immutable once
* set.
*/
@visibility(Lifecycle.Read)
@summary("Start of period")
start_at: Shared.DateTime;

/**
* End of the period (exclusive).
*
* Unset while the period is `open`. Fixed at the moment a close is initiated, and
* immutable thereafter.
*/
@visibility(Lifecycle.Read)
@summary("End of period")
end_at?: Shared.DateTime;

/**
* Current lifecycle status of the period.
*/
@visibility(Lifecycle.Read)
@summary("Status")
status: AccountingPeriodStatus;

/**
* Timestamp when the close was initiated (open → closing transition). Set on
* `closing` and `closed` periods.
*/
@visibility(Lifecycle.Read)
@summary("Closing started at")
closing_started_at?: Shared.DateTime;

/**
* Timestamp when the period was marked `closed` (closing → closed transition). Set
* only on `closed` periods.
*/
@visibility(Lifecycle.Read)
@summary("Closed at")
closed_at?: Shared.DateTime;

/**
* Timestamp when the seal hash was committed. Set only on `closed` periods that
* have a seal hash.
*/
@visibility(Lifecycle.Read)
@summary("Sealed at")
sealed_at?: Shared.DateTime;

/**
* Cryptographic hash sealing the period. The hash covers the period's frozen
* transaction membership and closing balance snapshots in a deterministic,
* versioned input format described by `seal_algorithm` and `seal_input_version`.
*
* Set only on `closed` periods that have been sealed.
*/
@visibility(Lifecycle.Read)
@summary("Seal hash")
seal_hash?: string;

/**
* Algorithm used to compute `seal_hash`. Example: `"sha256"`.
*/
@visibility(Lifecycle.Read)
@summary("Seal algorithm")
seal_algorithm?: string;

/**
* Version of the seal input format. Versioning the input format lets verifiers
* reproduce a historical seal even after the format evolves.
*/
@visibility(Lifecycle.Read)
@summary("Seal input version")
seal_input_version?: string;
}
10 changes: 10 additions & 0 deletions api/spec/packages/aip/src/konnect.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "./billing/index.tsp";
import "./tax/index.tsp";
import "./defaults/index.tsp";
import "./governance/index.tsp";
import "./accounting/index.tsp";

using TypeSpec.Http;
using TypeSpec.OpenAPI;
Expand Down Expand Up @@ -59,6 +60,10 @@ using TypeSpec.OpenAPI;
Shared.GovernanceTag,
#{ description: Shared.GovernanceDescription }
)
@tagMetadata(
Shared.AccountingTag,
#{ description: Shared.AccountingDescription }
)
@useAuth(systemAccountAccessToken | personalAccessToken | konnectAccessToken)
namespace MeteringAndBilling;

Expand Down Expand Up @@ -197,6 +202,11 @@ interface OrganizationDefaultTaxCodesEndpoints
@tag(Shared.GovernanceTag)
interface GovernanceEndpoints extends Governance.GovernanceOperations {}

@route("/openmeter/accounting/periods")
@tag(Shared.AccountingTag)
interface AccountingPeriodsEndpoints
extends Accounting.AccountingPeriodsOperations {}

/**
* The system account access token is meant for automations and integrations that
* are not directly associated with a human identity.
Expand Down
10 changes: 10 additions & 0 deletions api/spec/packages/aip/src/openmeter.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./llmcost/index.tsp";
import "./productcatalog/index.tsp";
import "./defaults/index.tsp";
import "./governance/index.tsp";
import "./accounting/index.tsp";

using TypeSpec.Http;
using TypeSpec.OpenAPI;
Expand Down Expand Up @@ -65,6 +66,10 @@ using TypeSpec.OpenAPI;
Shared.GovernanceTag,
#{ description: Shared.GovernanceDescription }
)
@tagMetadata(
Shared.AccountingTag,
#{ description: Shared.AccountingDescription }
)
namespace OpenMeter;

@route("/openmeter/events")
Expand Down Expand Up @@ -201,3 +206,8 @@ interface OrganizationDefaultTaxCodesEndpoints
@route("/openmeter/governance")
@tag(Shared.GovernanceTag)
interface GovernanceEndpoints extends Governance.GovernanceOperations {}

@route("/openmeter/accounting/periods")
@tag(Shared.AccountingTag)
interface AccountingPeriodsEndpoints
extends Accounting.AccountingPeriodsOperations {}
3 changes: 3 additions & 0 deletions api/spec/packages/aip/src/shared/consts.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const DefaultsDescription = "Organization-level default configuration.";
const GovernanceTag = "OpenMeter Governance";
const GovernanceDescription = "Governance evaluation of customers to check their feature access.";

const AccountingTag = "OpenMeter Accounting";
const AccountingDescription = "Accounting manages the accounting periods, transactions, and revenue recognition.";

// The functionality should be considered unstable.
const UnstableExtension = "x-unstable";
// The functionality is internal and should not be used by customers.
Expand Down
Loading
Loading