Apps: Verbrauch melden (Usage)

Die installierte App meldet Verbrauchseinheiten; idempotente Einreichung, atomische Kap-Abbuchung und eindeutige Antwort.

Für Entwickler

Nur die App-Installation darf diesen Endpunkt aufrufen (appIdentityOnly). Die Route erwartet ein strukturiertes JSON im Rumpf und führt atomische Prüfungen gegen die aktive Abrechnungsperiode durch.

Die Route

POST/api/v1/app/usage

Meldet usage-Events der installierten App und beansprucht das vereinbarte Kontingent atomar

Rechte

Keine — jeder gültige Schlüssel darf das.

Felder im Rumpf

NameTypBedeutung
idempotencyKeyPflichtstring [A-Za-z0-9._:-]+Eindeutiger Schlüssel zur Idempotenz; gleiches Payload + Key führt zur Erkennung eines Replays
quantityPflichtinteger >= 1Anzahl der Einheiten, die abgerechnet werden sollen
descriptionPflichtstringFreitextbeschreibung, wird im Usage-Record gespeichert
periodStartPflichtISO-8601 UTCBeginn der abzurechnenden Periode
periodEndPflichtISO-8601 UTCEnde der Periode; muss > periodStart sein

Mögliche Fehler

  • validationSchema verletzt (z. B. periodEnd ≤ periodStart oder ungültiger idempotencyKey)
  • Die Implementierung führt atomisch die Kap-Claim-Prüfung und das Anlegen des Usage-Records durch; bei Duplikaten liefert die Antwort `{ id, amountCents, replayed: true }`.
  • Idempotency wird per `idempotencyKey` + kanonischem Payload geprüft; wiederholte Einsendungen mit gleichem Hash werden als Replays zurückgegeben.
  • Keine zusätzlichen Module oder `PERSONAL`-Schlüssel: die Route ist ausschließlich für die App-Installation gedacht.
  • Fachliche Erweiterungsfehler tragen extensions.errors.conflict beziehungsweise extensions.errors.notFound; diese gehören nicht zum allgemeinen API-Fehlerkatalog. Ein Limitkonflikt hat Status 409, eine fehlende aktive Periode Status 404.
  • Die Beispielperiode muss durch currentPeriodStart/currentPeriodEnd aus GET /api/v1/app ersetzt werden. Nur der offene aktuelle Zeitraum ist abrechenbar.

Codebeispiele

curl — idempotente Usage senden
curl
curl -sS https://tactictable.com/api/v1/app/usage \  -H "Authorization: Bearer tt_live_if3u5vp6maf67xmw_PT1xP8EQF7reKUtLcZ7v9z5qaRKmoxeHmd27JpaDvfM" \  -H "Content-Type: application/json" \  -d '{"idempotencyKey":"key-12345","quantity":10,"description":"monthly sync","periodStart":"2026-09-01T00:00:00Z","periodEnd":"2026-09-30T23:59:59Z"}'
`idempotencyKey` verhindert doppelte Abbuchungen. Token gehört in die Kopfzeile, niemals in die URL.
Antwort 200 — erfolgreich oder Replay
JSON
{    "id": "31061d44-ac33-550d-b4d0-a16973e270f5",    "amountCents": 2500,    "replayed": false}
Bei Replays ist replayed true und amountCents zeigt denselben gebuchten Betrag. Ein Record ist ein akzeptierter Verbrauch, noch kein Zahlungseingang.
TypeScript — fetch (Server)
TypeScript
const token = process.env.TACTICTABLE_TOKEN ?? '' const payload = {    idempotencyKey: 'key-12345',    quantity: 10,    description: 'monthly sync',    periodStart: '2026-09-01T00:00:00Z',    periodEnd: '2026-09-30T23:59:59Z',} const resp = await fetch('https://tactictable.com/api/v1/app/usage', {    method: 'POST',    headers: {        Authorization: 'Bearer ' + token,        'Content-Type': 'application/json',        Accept: 'application/json',    },    body: JSON.stringify(payload),}) const body = await resp.json()if (!resp.ok) throw new Error(body.error + ': ' + body.message) console.log(body.id, body.amountCents, body.replayed)
Achten Sie auf atomare Prüfung und Logging der `idempotencyKey`-Werte.
Python — urllib / requests
Python
import osimport requests token = os.environ['TACTICTABLE_TOKEN']payload = {    'idempotencyKey': 'key-12345',    'quantity': 10,    'description': 'monthly sync',    'periodStart': '2026-09-01T00:00:00Z',    'periodEnd': '2026-09-30T23:59:59Z',} r = requests.post(    'https://tactictable.com/api/v1/app/usage',    json=payload,    headers={'Authorization': 'Bearer ' + token, 'Accept': 'application/json'},    timeout=15,)body = r.json()if r.status_code >= 400:    raise SystemExit(f"{body.get('error')}: {body.get('message')} [{body.get('requestId')}]") print(body['id'], body['amountCents'], body['replayed'])
Setzen Sie immer ein Timeout und protokollieren Sie `requestId` für Supportfälle.