For the complete documentation index, see llms.txt. This page is also available as Markdown.

Cost Center Admin API

Manage a tenant's valid project codes and the cost-center validation toggle.

Scope

Admin API for managing a tenant's valid project codes and the cost-center validation toggle: how to authenticate, the single-code and batch CRUD endpoints, bulk CSV/JSON import with a dry-run preview, value validation, and the config toggle.

Per-endpoint reference is intentionally condensed here. The live, always-current reference lives at analytics.theblockbrain.ai/docs (Scalar, auto-rendered from the OpenAPI spec). This page covers the narrative, auth model, and request/response examples.


Overview

The Cost Center Admin API manages a tenant's list of valid project codes and the cost-center validation toggle. Codes arrive through the single-code REST endpoint, the JSON batch endpoints, or a bulk CSV/JSON import, and any candidate value can be checked against the tenant's active codes.

Base URL: https://analytics.theblockbrain.ai

Base path: /api/v1/admin/cb-limit/cost-center

Property
Value

Base path

/api/v1/admin/cb-limit/cost-center

Host

https://analytics.theblockbrain.ai

Auth

Bearer JWT or API key (sk-), role admin. The acted-on tenant is derived from the token.

When is validation enforced? Only when the tenant's validation toggle is on (see PUT /config) and at least one active code is configured. Otherwise any value is accepted. Each JSON batch endpoint accepts up to 10,000 codes per request. Use /import for larger loads.


API summary

Every endpoint the Cost Center Admin API exposes. Paths are relative to the base path /api/v1/admin/cb-limit/cost-center. Full request and response detail is in Scalar.

Group
Method
Endpoint
Description

Codes

GET

/codes

List the tenant's project codes (paginated, searchable, sortable).

Codes

POST

/codes

Add or update a single project code (idempotent upsert).

Codes

DELETE

/codes

Delete a project code by its value.

Codes

POST

/codes/batch

Add or update many codes in one request.

Codes

POST

/codes/batch-set-active

Activate or deactivate many codes (pause or resume).

Codes

POST

/codes/batch-delete

Delete many codes in one request.

Validation

POST

/validate

Check a value against the tenant's active codes.

Import

POST

/import

Bulk import codes from a CSV or JSON file.

Import

POST

/import/preview

Dry-run an import and classify every row.

Config

GET

/config

Read the validation toggle.

Config

PUT

/config

Set the validation toggle on or off.


Authentication

Every endpoint expects a Bearer token in the Authorization header:

Two kinds of credential are accepted, and both go through the same header:

Credential
Looks like
How it is verified

Zitadel JWT

a JWT (header.payload.signature)

Verified against Zitadel (JWKS, with token introspection as a fallback). This is the existing access-token path.

API key

sk- prefixed key

Introspected through Blocky. The key authorizes by its own role, so it must carry the admin role, the same bar a user token has to clear.

Every example below uses $TOKEN, which can hold either credential. A missing or malformed token returns 401, and a token whose role is below admin returns 403.


Shared schemas

ProjectCode

label is nullable. source is one of rest-api, csv-import, json-import, admin-manual. Codes match case-insensitively (trimmed, lower-cased), and the display casing is whatever was last written.

Problem (422, application/problem+json)

Returned only by POST /validate when the value does not match an active code. All other errors return a plain { "error": "…" } JSON body.


Codes

GET /codes

List the tenant's project codes. Paginated list with a total count. The admin view shows paused codes too. Default sort is newest first.

Query parameters

Name
Type
Required
Default
Description

search

string

No

Case-insensitive substring match on code or label. Minimum length 1.

activeOnly

"true" | "false"

No

false

Pass true to hide paused codes (what the dropdown does).

from

date (ISO)

No

Inclusive lower bound on createdAt.

to

date (ISO)

No

Exclusive upper bound on createdAt.

page

integer ≥ 1

No

1

1-based page number.

pageSize

integer 1..100

No

25

Rows per page. Values above 100 return 400.

sortBy

code | createdAt | active

No

createdAt

Sort column.

sortOrder

asc | desc

No

desc

Sort direction.

Responses

Status
Body
When

200

{ items, totalCount, page, pageSize }

Paginated project codes.

400

{ error }

Invalid query parameters.

401

Missing or invalid token.

403

Missing the cost-controls permission.

Request

Response (200)

POST /codes

Add or update a single project code. Idempotent upsert on the case-insensitive code. This is the endpoint a client's automation calls to keep codes in sync: create on project start, pause with active: false. Re-adding a code refreshes its casing, label, and active flag but keeps its id and audit columns.

Omitting active resets it to true, so a re-add reactivates a paused code.

Body · application/json

Field
Type
Required
Description

code

string 1..256

Yes

The project code. Trimmed before storing.

label

string ≤ 512

No

Human label. Defaults to null.

active

boolean

No

Defaults to true. Set false to pause without deleting.

Responses

Status
Body
When

200

ProjectCode

The upserted code.

400

{ error }

Code missing, blank, or too long.

401 / 403

Unauthorized or forbidden.

Request

Response (200)

DELETE /codes

Delete a project code. Hard-deletes the code matching code (case-insensitive) for the calling tenant. The client's automation calls this when a project closes.

Query parameters

Name
Type
Required
Description

code

string

Yes

The code value to delete. Minimum length 1.

Responses

Status
Body
When

200

{ "deleted": true }

A row was removed.

400

{ error }

The code query parameter is missing.

404

{ error }

No code matched.

401 / 403

Unauthorized or forbidden.

Request

POST /codes/batch

Add or update project codes (batch). The multi-code sibling of POST /codes. Upserts many codes in one JSON request (idempotent, case-insensitive). Each entry carries its own label and active (defaults true). Duplicate codes within the request are collapsed case-insensitively, first wins (same as the file import). Returns the count of unique codes written. Use /import for very large file loads.

Body · application/json

Field
Type
Required
Description

codes

array 1..10000

Yes

Non-empty array of code entries.

codes[].code

string 1..256

Yes

The project code.

codes[].label

string ≤ 512

No

Human label.

codes[].active

boolean

No

Defaults to true.

Responses

Status
Body
When

200

{ "upserted": number }

Count of unique codes written.

400

{ error }

Empty array, over 10,000 entries, or a blank/oversized code.

401 / 403

Unauthorized or forbidden.

Request

Response (200)

POST /codes/batch-set-active

Activate or deactivate project codes (batch). Flips only the active flag of every listed code (case-insensitive). Unlike POST /codes, it never rewrites the label or source, so a multi-select pause or resume keeps each code's display metadata intact. Deactivating pauses codes: they stop validating and leave the dropdown without being deleted. Returns the number of existing codes updated (codes that do not exist are ignored).

Body · application/json

Field
Type
Required
Description

codes

string[] 1..10000

Yes

Non-empty array of code values, each 1..256 chars.

active

boolean

Yes

true to activate, false to pause.

Responses

Status
Body
When

200

{ "updated": number }

Count of existing codes updated.

400

{ error }

Empty array, missing or non-boolean active, or an oversized code.

401 / 403

Unauthorized or forbidden.

Request

Response (200)

POST /codes/batch-delete

Delete project codes (batch). Hard-deletes every listed code (case-insensitive) in one request. Backs the admin UI's multi-select delete. Returns the number actually removed, which may be fewer than requested when some codes did not exist.

Body · application/json

Field
Type
Required
Description

codes

string[] 1..10000

Yes

Non-empty array of code values, each 1..256 chars.

Responses

Status
Body
When

200

{ "deleted": number }

Count of codes actually removed.

400

{ error }

Empty array, over 10,000 entries, or an oversized code.

401 / 403

Unauthorized or forbidden.

Request

Response (200)


Validation

POST /validate

Validate a project code. Returns { "valid": true } when the value matches one of the tenant's active codes. Matching is case-insensitive and trims whitespace. A paused code counts as invalid.

Body · application/json

Field
Type
Required
Description

value

string

Yes

The value to check. Minimum length 1 after trimming.

field

string

No

Present in the contract. Only project-code is validated today.

Responses

Status
Body
When

200

{ "valid": true }

The value matches an active code.

400

{ error }

value is missing or blank.

422

Problem (problem+json)

The value is not a valid project code.

401 / 403

Unauthorized or forbidden.

Request

Response (422)


Import

POST /import

Import project codes from a CSV or JSON file. Multipart upload. Codes are trimmed, blank rows skipped, and in-file duplicates collapsed case-insensitively (first wins). Each code is upserted so re-importing is idempotent. Imported codes are always set active. A row whose code exceeds 256 characters or whose label exceeds 512 is reported as an error row and not written (mirrors the API field caps). Max 5 MB and 200,000 rows.

Body · multipart/form-data

Field
Type
Required
Description

file

binary

Yes

CSV (header row with a code column, optional label) or JSON.

format

"csv" | "json"

Yes

How to parse the file.

Accepted JSON shapes

Responses

Status
Body
When

200

{ imported, duplicatesInFile, errors[] }

Import summary. Each error carries { row, code, reason }.

400

{ error }

File missing, bad format, or unparseable (no code column, invalid JSON, wrong shape).

413

{ error }

File exceeds the size or row limit.

401 / 403

Unauthorized or forbidden.

Request

Response (200)

POST /import/preview

Preview a CSV/JSON import without writing anything. Parses the file and classifies every row in file order (new, update, duplicate, error) so the admin can review before importing. A row is an error when its code is empty or its code/label exceeds the field caps (256 / 512). update versus new is resolved against the tenant's existing codes. Writes nothing. The entries array is capped at 2000 rows, but the summary counts every row.

Body · multipart/form-data

Field
Type
Required
Description

file

binary

Yes

Same file shapes as /import.

format

"csv" | "json"

Yes

How to parse the file.

Responses

Status
Body
When

200

{ entries[], totalRows, truncated, summary }

Per-row classification plus a summary over all rows.

400

{ error }

File missing, bad format, or unparseable.

413

{ error }

File exceeds the size or row limit.

401 / 403

Unauthorized or forbidden.

Response (200)


Config

GET /config

Get the tenant's cost-center validation toggle. Returns whether validation is switched on for the calling tenant. A tenant with no settings row reports false.

Responses

Status
Body
When

200

{ "validationEnabled": boolean }

Toggle state.

401 / 403

Unauthorized or forbidden.

Request

Response (200)

PUT /config

Set the tenant's cost-center validation toggle. Switches validation on or off. Turning it on for the first time seeds a settings row from the system default. Turning it off, or back on, only touches this toggle and the audit columns and preserves all other config. Validation enforces only once the toggle is on and at least one active code is configured.

Body · application/json

Field
Type
Required
Description

validationEnabled

boolean

Yes

The new toggle state.

Responses

Status
Body
When

200

{ "validationEnabled": boolean }

Updated toggle state.

400

{ error }

validationEnabled is missing or not a boolean.

401 / 403

Unauthorized or forbidden.

Request


Last updated