Skip to content

Charges (trusted apps) ​

Trusted apps can book single items onto a user's open monthly bill of the app – e.g. each item a shipping app sends: one line per charge, no subscription, charged with the bill on the 1st like every other line, with invoice and VAT by Anikeen ID.

For a separate one-off purchase charged on its own, create an order instead.

Requirements:

  • Anikeen has to enable charging for your app and sets a maximum per charge (net).
  • The user must be connected to your app.
  • An app token (client credentials) with billing:client.

The user's controls are their billing limit (0.00 stops all charges) and removing your app's access.

Endpoints ​

MethodPath
POST/api/v1/client/users/{user}/chargesbook; 201, an idempotent retry 200
GET/api/v1/client/users/{user}/charges/{charge}the charge
POST/api/v1/client/users/{user}/charges/{charge}/voidvoid it while its bill is open; reason? (at most 255 characters)

Booking a charge ​

shell
curl -X POST https://id.anikeen.com/api/v1/client/users/01jexample/charges \
  -H 'Authorization: Bearer APP_TOKEN' \
  -H 'Accept: application/json' -H 'Content-Type: application/json' \
  -d '{
    "idempotency_key": "shipment-123",
    "name": "Signed card",
    "type": "physical",
    "quantity": 2,
    "unit_price": "4.00",
    "reference": "order-77"
  }'
Field
idempotency_keyrequired, at most 255 characters; unique per app and user
namerequired, at most 255 characters: the line on the bill
descriptionoptional, at most 255 characters
typedigital (default), physical or physical-reduced: the VAT treatment
quantityrequired integer 1–10000
unit_pricerequired, net per piece, > 0, at most 100000, up to 6 decimals; quantity × unit price rounded to cents must be at least 0.01
currency_isoEUR only (default)
groupoptional: null or a group in which your app already bills the user
occurred_atoptional date, not in the future, at most 31 days ago (default: now): the service date
referenceoptional, at most 255 characters: your own ID, not on the bill
metadataoptional object, at most 20 keys (at most 40 characters each), string values of at most 500 characters
image_urloptional http(s) URL, copied like order item pictures

reference and metadata are your data about the user, kept with the charge and not shown on the bill: put nothing sensitive there.

json
{
  "data": {
    "object": "charge",
    "id": "01J…",
    "user_id": "01j…",
    "status": "open",
    "idempotency_key": "shipment-123",
    "name": "Signed card",
    "description": null,
    "type": "physical",
    "quantity": 2,
    "unit": "piece",
    "unit_price": "4.000000",
    "net_amount": "8.00",
    "gross_amount": "9.52",
    "currency_iso": "EUR",
    "group": null,
    "occurred_at": "…",
    "reference": "order-77",
    "metadata": {},
    "order_id": "01J…",
    "voided_at": null,
    "void_reason": null,
    "created_at": "…"
  }
}

status is open (its bill is open), billed (the bill was closed) or voided. gross_amount is what the charge added to the bill, order_id the monthly bill it is on.

php
$anikeen = app(\Anikeen\Id\AnikeenId::class);

$charge = $anikeen->createChargeForUser($user->anikeen_id, [
    'idempotency_key' => "shipment-{$shipment->id}",
    'name' => 'Signed card',
    'type' => 'physical',
    'quantity' => 2,
    'unit_price' => '4.00',
    'reference' => "order-{$shipment->order_id}",
]);

$charge->id();          // keep it to void the charge later
$charge->grossAmount(); // "9.52"
$charge->orderId();
$charge->status();      // open, billed or voided

$anikeen->findChargeForUser($user->anikeen_id, $chargeId);
$anikeen->voidChargeForUser($user->anikeen_id, $chargeId, 'Parcel lost');

Rules ​

  • Idempotency: retrying with the same key and the same fields returns the existing charge (200 instead of 201), even after the limit was reached. The same key with other fields is 409 idempotency_key_conflict (with charge_id). Use one key per item you charge for.
  • Maximum: a charge above your app's maximum (net) is 409 charge_exceeds_maximum with max_charge_net.
  • Group: null, or a group your app already bills the user in (a running subscription or an open monthly bill of your app); others are 409 group_not_allowed.
  • Billing address: the user needs one (409 billing_address_missing).
  • Billing limit: a charge that would take the user's open bills above their billing limit is refused with 409 billing_limit_reached. Exactly reaching the limit is allowed.
  • Voiding removes the line while the bill is open (an already voided charge is returned as it is). Once the bill was closed: 409 charge_already_billed; apps can't refund billed charges.

A refused charge ​

json
HTTP/1.1 409 Conflict

{
  "message": "The customer's billing limit is reached.",
  "error": "billing_limit_reached",
  "limit": "100.00",
  "used": "97.40",
  "remaining": "2.60",
  "remaining_net": "2.18",
  "required": "4.76",
  "currency": "EUR",
  "resets_at": "2026-12-01T02:00:00Z"
}

used and remaining are before the refused charge, required is its gross amount. A 409 while room remains refuses only this charge: decide from remaining (e.g. a smaller one).

php
use Anikeen\Id\Exceptions\ApiException;

try {
    $charge = $anikeen->createChargeForUser($user->anikeen_id, $attributes);
} catch (ApiException $e) {
    if ($e->isBillingLimitReached()) {
        $e->body()->remaining;  // room left before this charge, e.g. "2.60"
        $e->body()->required;   // the refused charge, gross
        $e->body()->resets_at;  // when the limit has room again at the latest
        return $shipment->holdForBillingLimit();
    }

    match ($e->error) {
        'charge_exceeds_maximum' => report($e),   // $e->body()->max_charge_net
        'group_not_allowed' => report($e),
        'billing_address_missing' => $shipment->askForBillingAddress(),
        default => throw $e,
    };
}

Errors ​

StatuserrorWhen
401, 403–no or an invalid token
403client_not_allowednot a trusted app with its own token
403charges_not_enabledcharging is not enabled for your app (also for reads and voids)
404user_not_foundunknown, deleted or not connected user
404charge_not_foundno such charge of this user and app
409billing_limit_reachedabove the user's billing limit (body above)
409charge_exceeds_maximumabove your app's maximum (+ max_charge_net)
409group_not_allowedyour app doesn't bill the user in that group
409idempotency_key_conflictthe key was used for another charge (+ charge_id)
409billing_address_missingthe user has no billing address
409charge_already_billedvoid after the bill was closed (+ charge_id)
422–validation (errors)
429too_many_requestsrate limited per app and user, with Retry-After

On the bill ​

Charge lines are ordinary lines of your app's monthly bill: invoices show them with net price, VAT and the service date. App orders list them with charge_id, also in the order webhooks.

Anikeen ID developer documentation