Siren

Obligations

Payment obligations owed to collaborators — data model, status lifecycle, REST API, and PHP data access.

Last updated: April 9, 2026

Obligations

An obligation represents a debt the business owes a collaborator as a result of an approved conversion. Obligations sit downstream of conversions and upstream of fulfillments. They are the “IOU” in Siren’s attribution pipeline. When a conversion is approved, the incentive system evaluates the program’s rules and creates an obligation for the calculated reward amount. Once obligations are gathered into a fulfillment batch and paid out, their status transitions to fulfilled.

The obligation object

FieldREST namePHP getterTypeDescription
IDidgetId()integerUnique identifier
CollaboratorcollaboratorIdgetCollaboratorId()integerThe collaborator this obligation is owed to
StatusstatusgetStatus()stringCurrent status (see lifecycle below)
Award typeawardTypegetAwardType()stringHow the reward was calculated (e.g., commission, flat)
ValuevaluegetValue()integerReward amount in the smallest currency unit (e.g., cents)
PayoutpayoutIdgetPayoutId()integer or nullThe payout this obligation was fulfilled through, if any
CreateddateCreatedgetCreatedDate()datetimeWhen the record was created
ModifieddateModifiedgetModifiedDate()datetimeWhen the record was last updated

The value field stores amounts as integers in the smallest currency unit. A $15.00 commission is stored as 1500. This avoids floating-point precision issues in financial calculations.

The payoutId is null until the obligation is included in a payout batch. Once a payout is processed and the obligation is marked fulfilled, the payoutId links it to the payout record that settled the debt.

Extended fields (REST only, via ?fields=)

These fields are resolved dynamically by the REST API field resolver system and must be explicitly requested.

List endpoint (GET /obligations): collaboratorName, collaboratorEmail

Detail endpoint (GET /obligations/{id}): collaboratorName, collaboratorEmail, conversionId, conversionType, conversionDate, conversionAmount, programId, programName

Status lifecycle

Draft

A conversion is recorded but not yet approved. The obligation exists but is not actionable.

Pending

The conversion has been approved and the obligation is awaiting fulfillment.

Fulfilled

Included in a fulfillment batch and linked to a payout record.

StatusDescription
draftInitial state when a conversion is recorded but not yet approved. The obligation exists but is not actionable.
pendingThe conversion has been approved and the obligation is awaiting fulfillment.
fulfilledIncluded in a fulfillment batch. The payoutId field links it to the specific payout record.
rejectedThe linked conversion was rejected (e.g., due to a refund). The admin can cancel or leave pending depending on refund policy.
cancelledSoft deleted. A second DELETE request permanently removes the record.

See How Refunds Work for a complete walkthrough of how refunds cascade through conversions and obligations.

Accessing obligation data

# List pending obligations for a collaborator
curl -X GET "https://your-site.com/wp-json/siren/v1/obligations?collaboratorId=42&status=pending&fields=id,value,status,awardType" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Get a single obligation with extended fields
curl -X GET "https://your-site.com/wp-json/siren/v1/obligations/15?fields=id,value,status,collaboratorName,programName" \
  -H "Authorization: Bearer YOUR_TOKEN"
use Siren\Obligations\Core\Datastores\Obligation\Interfaces\ObligationDatastore;

class PayoutReport
{
    protected ObligationDatastore $obligations;

    public function __construct(ObligationDatastore $obligations)
    {
        $this->obligations = $obligations;
    }

    public function getPendingObligations(int $collaboratorId): array
    {
        return $this->obligations->andWhere([
            ['column' => 'collaboratorId', 'operator' => '=', 'value' => $collaboratorId],
            ['column' => 'status', 'operator' => '=', 'value' => 'pending']
        ]);
    }
}
use Siren\Obligations\Core\Facades\Obligations;

$pending = Obligations::andWhere([
    ['column' => 'collaboratorId', 'operator' => '=', 'value' => $collaboratorId],
    ['column' => 'status', 'operator' => '=', 'value' => 'pending']
]);

$obligation = Obligations::getById(42);

Siren is built on an event-driven architecture. Creating obligations manually via PHP bypasses the incentive calculation. The program’s reward rules won’t be applied, the conversion won’t be linked, and the ObligationIssued event won’t fire. Downstream listeners that bind obligations to conversions and trigger fulfillment workflows depend on that event. If you need to issue an obligation, let the pipeline handle it by approving the conversion.

PHP domain methods

Looking up an obligation by conversion

getByConversionId retrieves the obligation that was created for a specific conversion. This is useful when you have a conversion record and need to check whether an obligation was issued and what its current state is. This method is only available through dependency injection.

use Siren\Obligations\Core\Datastores\Obligation\Interfaces\ObligationDatastore;
use PHPNomad\Datastore\Exceptions\RecordNotFoundException;

class ConversionInspector
{
    protected ObligationDatastore $obligations;

    public function __construct(ObligationDatastore $obligations)
    {
        $this->obligations = $obligations;
    }

    public function getObligationForConversion(int $conversionId): ?Obligation
    {
        try {
            return $this->obligations->getByConversionId($conversionId);
        } catch (RecordNotFoundException $e) {
            return null;
        }
    }
}

Not every conversion results in an obligation. The incentive system may determine the conversion doesn’t qualify, or the obligation may not have been created yet if the conversion is still pending.

If you already have the conversion’s obligationId, you can skip this lookup and use getById directly:

$obligationId = $conversion->getObligationId();

if ($obligationId !== null) {
    $obligation = $this->obligations->getById($obligationId);
}

Querying by payout

To find all obligations that were settled in a specific payout batch:

$settled = $this->obligations->andWhere([
    ['column' => 'payoutId', 'operator' => '=', 'value' => $payoutId]
]);

Summing owed amounts

There is no built-in sum method, but you can combine a filtered query with array reduction:

$pending = $this->obligations->andWhere([
    ['column' => 'collaboratorId', 'operator' => '=', 'value' => $collaboratorId],
    ['column' => 'status', 'operator' => '=', 'value' => 'pending']
]);

$totalOwed = array_reduce($pending, fn(int $sum, Obligation $o) => $sum + $o->getValue(), 0);

Access control (REST)

The list endpoint uses AutoFilterByOwnerMiddleware to enforce access control. Admin users see all obligations across all collaborators, while collaborator-role users are automatically filtered to see only obligations linked to their own collaboratorId. The middleware maps the authenticated user to their collaborator record and injects the filter before the query executes, which is how the collaborator portal shows “my earnings” without exposing other collaborators’ data.

Relationships

Every obligation belongs to exactly one collaborator via collaboratorId.

Upstream, obligations are created as a downstream effect of an approved conversion. The link between the two is maintained via the conversion’s obligationId field.

When an obligation is fulfilled, it is linked to a payout record via payoutId. The payout tracks the actual disbursement to the collaborator. Payouts are grouped into fulfillment batches, so the obligation connects to the fulfillment indirectly through its payout.

REST endpoints

See the individual endpoint pages in the sidebar for full request and response details, or browse the all REST endpoints reference.