Skip to main content
Subscribd includes an optional REST API that exposes billing data over standard HTTP endpoints. The API is useful when you need to integrate billing into a frontend SPA, mobile app, or external service that cannot call PHP directly. All endpoints live under a configurable prefix (default /subscribd/api/v1) and require bearer token authentication.

Authentication

The REST API uses hashed bearer tokens. Tokens are issued through an authenticated endpoint and passed in the Authorization header on subsequent requests.
Authorization: Bearer {your-token}
Tokens are stored as SHA-256 hashes. The plaintext token is returned once at creation and cannot be recovered.

Token abilities

Tokens support optional ability scoping. When a token has no abilities (or abilities is null), it acts as a wildcard and grants access to all endpoints. Otherwise, each ability must be explicitly listed. The special * ability also grants full access.

Token expiration

Tokens can have an optional expires_at date. Expired tokens are rejected automatically. Tokens without an expiry never expire.

Setup

Add the trait to your billable model

The ManagesBilling trait automatically includes the apiTokens() relationship. If you are already using ManagesBilling, no additional setup is needed. For models that only need API token support without full billing, add the HasApiTokens trait and define the apiTokens() relationship manually:
use Pixelworxio\Subscribd\Concerns\HasApiTokens;
use Pixelworxio\Subscribd\Models\ApiToken;

class User extends Authenticatable
{
    use HasApiTokens;

    public function apiTokens(): MorphMany
    {
        return $this->morphMany(ApiToken::class, 'tokenable');
    }
}

Configure the API

The API is enabled by default. You can customize the path and middleware in config/subscribd.php:
'api' => [
    'enabled'          => true,
    'path'             => 'subscribd/api/v1',
    'middleware'       => ['api'],
    'token_middleware' => ['auth'],
],
KeyTypeDefaultDescription
enabledbooltrueEnable or disable the REST API routes.
pathstring'subscribd/api/v1'URL prefix for all API routes.
middlewarearray['api']Middleware applied to bearer-protected endpoints.
token_middlewarearray['auth']Middleware applied to token issuance and revocation endpoints. Must resolve $request->user().
The token_middleware must authenticate the user through your application’s own auth guard (e.g., auth:web, auth:sanctum). This is separate from the bearer token middleware that protects the other API endpoints.

Token management

Issue a token

POST /tokens
Requires host authentication (configured via token_middleware). Request body:
ParameterTypeRequiredDescription
namestringYesA human-readable label for the token (max 255 characters).
abilitiesstring[]NoList of abilities to scope the token. Omit for full access.
expires_atstringNoISO 8601 datetime for token expiry. Must be in the future. null for no expiry.
Example request:
curl -X POST https://your-app.com/subscribd/api/v1/tokens \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mobile App",
    "abilities": ["subscriptions:read", "invoices:read"],
    "expires_at": "2026-12-31T23:59:59Z"
  }'
Response 201 Created:
{
  "data": {
    "id": 1,
    "name": "Mobile App",
    "token": "abc123...plaintext-only-shown-once",
    "abilities": ["subscriptions:read", "invoices:read"],
    "expires_at": "2026-12-31T23:59:59+00:00",
    "created_at": "2026-04-15T12:00:00+00:00"
  }
}
The plaintext token value is only returned once. Store it securely — it cannot be recovered from the stored hash.

Revoke a token

DELETE /tokens/{id}
Requires host authentication. You can only revoke your own tokens. Response 204 No Content Error responses:
StatusDescription
401Not authenticated.
404Token not found or belongs to another user.

Plans

List plans

GET /plans
Returns all active plans ordered by sort_order. Response 200 OK:
{
  "data": [
    {
      "id": 1,
      "key": "starter",
      "name": "Starter",
      "description": "Full product access for individuals.",
      "price": 1900,
      "currency": "USD",
      "interval": "month",
      "interval_count": 1,
      "trial_days": 14,
      "features": {
        "projects": 5,
        "api_access": true,
        "support": "email"
      },
      "is_recurring": true,
      "sort_order": 1
    }
  ]
}

Get a plan

GET /plans/{id}
Returns a single plan. Returns 404 if the plan is inactive. Response 200 OK:
{
  "data": {
    "id": 1,
    "key": "starter",
    "name": "Starter",
    "description": "Full product access for individuals.",
    "price": 1900,
    "currency": "USD",
    "interval": "month",
    "interval_count": 1,
    "trial_days": 14,
    "features": {
      "projects": 5,
      "api_access": true,
      "support": "email"
    },
    "is_recurring": true,
    "sort_order": 1
  }
}

Plan object

FieldTypeDescription
idintegerUnique plan identifier.
keystringConfig-defined plan key.
namestringDisplay name.
descriptionstring|nullPlan description.
priceintegerPrice in minor currency units (e.g., cents).
currencystringISO 4217 currency code.
intervalstringBilling interval: day, week, month, or year.
interval_countintegerNumber of intervals per billing cycle.
trial_daysinteger|nullFree trial duration in days.
featuresobject|nullFeature flags and limits defined on the plan.
is_recurringbooleantrue for recurring plans, false for one-time/lifetime.
sort_orderintegerDisplay ordering.

Subscriptions

All subscription endpoints are scoped to the token owner. You can only view and manage your own subscriptions.

List subscriptions

GET /subscriptions
Returns a paginated list of the token owner’s subscriptions, newest first. Response 200 OK:
{
  "data": [
    {
      "id": 1,
      "name": "default",
      "gateway": "stripe",
      "gateway_id": "sub_abc123",
      "status": "active",
      "currency": "USD",
      "quantity": 1,
      "plan": {
        "id": 1,
        "key": "starter",
        "name": "Starter",
        "price": 1900,
        "currency": "USD",
        "interval": "month",
        "interval_count": 1,
        "trial_days": 14,
        "features": { "projects": 5 },
        "is_recurring": true,
        "sort_order": 1
      },
      "trial_ends_at": null,
      "current_period_start": "2026-04-01T00:00:00+00:00",
      "current_period_end": "2026-05-01T00:00:00+00:00",
      "ends_at": null,
      "canceled_at": null,
      "paused_at": null,
      "created_at": "2026-04-01T00:00:00+00:00",
      "updated_at": "2026-04-01T00:00:00+00:00"
    }
  ],
  "meta": {
    "total": 1,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1
  }
}

Get a subscription

GET /subscriptions/{id}
Returns a single subscription with the related plan. Returns 404 if the subscription does not belong to the token owner.

Cancel a subscription

POST /subscriptions/{id}/cancel
Cancels a subscription. By default, cancellation takes effect at the end of the current billing period. Request body:
ParameterTypeRequiredDescription
immediatelybooleanNoSet to true to cancel immediately instead of at period end. Defaults to false.
Example request:
curl -X POST https://your-app.com/subscribd/api/v1/subscriptions/1/cancel \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"immediately": true}'
Response 200 OK — returns the updated subscription object. Error responses:
StatusDescription
404Subscription not found or does not belong to the token owner.
422Subscription cannot be canceled in its current state.

Resume a subscription

POST /subscriptions/{id}/resume
Resumes a canceled (grace period) or paused subscription. Response 200 OK — returns the updated subscription object. Error responses:
StatusDescription
404Subscription not found or does not belong to the token owner.
422Subscription cannot be resumed in its current state.

Tally metered usage

POST /subscriptions/{id}/tally-usage
Triggers a mid-period metered usage tally for the subscription. Subscribd computes overage across all metered plan items for the requested billing window, subtracts any units already billed in prior tallies during the same period, and charges only the incremental delta. If no overage is due, the response returns data: null without creating an invoice. This endpoint is safe to call multiple times per period — delta billing ensures each unit of overage is charged exactly once. Request body:
ParameterTypeRequiredDescription
period_startstringNoISO 8601 timestamp for the start of the billing window (inclusive). Defaults to the subscription’s current_period_start.
period_endstringNoISO 8601 timestamp for the end of the billing window (exclusive). Defaults to the current time.
Example request:
curl -X POST https://your-app.com/subscribd/api/v1/subscriptions/1/tally-usage \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "period_start": "2026-04-01T00:00:00Z",
    "period_end": "2026-04-15T00:00:00Z"
  }'
Response 200 OK — when overage is billed:
{
  "data": {
    "id": 12,
    "subscription_id": 1,
    "gateway": "stripe",
    "gateway_id": "in_xyz789",
    "number": "INV-0012",
    "status": "paid",
    "currency": "USD",
    "subtotal": 4200,
    "tax": 0,
    "discount": 0,
    "total": 4200,
    "refunded_amount": 0,
    "pdf_url": "https://your-app.com/subscribd/invoice/12/pdf",
    "due_at": "2026-04-15T00:00:00+00:00",
    "paid_at": "2026-04-15T00:00:00+00:00",
    "created_at": "2026-04-15T00:00:00+00:00",
    "updated_at": "2026-04-15T00:00:00+00:00"
  }
}
Response 200 OK — when no overage is due:
{
  "data": null
}
Error responses:
StatusDescription
404Subscription not found or does not belong to the token owner.
422Validation failure, or the subscription has no current_period_start and period_start was not supplied.
The tally endpoint uses the same delta billing logic as the subscribd:tally-usage Artisan command. Usage records that contribute to an overage invoice are stamped with the invoice ID for audit purposes.

Upgrade a free subscription to a paid plan

POST /subscriptions/{id}/upgrade-from-free
Converts a free subscription to a paid plan on a real payment gateway. The old free subscription is canceled locally and a new paid subscription is created in a single transaction. The new subscription inherits the same name slot. Request body:
ParameterTypeRequiredDescription
plan_idintegerYesID of the paid plan to upgrade to.
gatewaystringNoPayment gateway driver name. Defaults to the configured default gateway. Cannot be null (the null driver).
quantityintegerNoSeat count for per-seat plans. Must be at least 1. Defaults to the quantity resolver result or 1.
Example request:
curl -X POST https://your-app.com/subscribd/api/v1/subscriptions/1/upgrade-from-free \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"plan_id": 3, "gateway": "stripe"}'
Response 201 Created — returns the new subscription object:
{
  "data": {
    "id": 2,
    "name": "default",
    "gateway": "stripe",
    "gateway_id": "sub_xyz789",
    "status": "active",
    "currency": "USD",
    "quantity": 1,
    "plan": {
      "id": 3,
      "key": "pro",
      "name": "Pro",
      "price": 4900,
      "currency": "USD",
      "interval": "month",
      "interval_count": 1,
      "trial_days": null,
      "features": { "projects": 50, "api_access": true },
      "is_recurring": true,
      "sort_order": 2
    },
    "trial_ends_at": null,
    "current_period_start": "2026-04-17T00:00:00+00:00",
    "current_period_end": "2026-05-17T00:00:00+00:00",
    "ends_at": null,
    "canceled_at": null,
    "paused_at": null,
    "created_at": "2026-04-17T00:00:00+00:00",
    "updated_at": "2026-04-17T00:00:00+00:00"
  }
}
Error responses:
StatusDescription
404Subscription not found or does not belong to the token owner.
422Subscription is not in free status, the target gateway is the null driver, or validation failed.
The old free subscription is canceled locally without calling any gateway API (the null gateway has no remote state). A SubscriptionCreated event fires for the new paid subscription.

Subscription object

FieldTypeDescription
idintegerUnique subscription identifier.
namestringSubscription slot name (e.g., default).
gatewaystringPayment gateway driver name.
gateway_idstring|nullGateway-specific subscription identifier.
statusstringCurrent status: active, trialing, past_due, grace, paused, canceled, incomplete, expired, free, or lifetime.
currencystringISO 4217 currency code.
quantityintegerSeat count (for per-seat plans).
planobject|nullNested plan object (included when the relation is loaded).
trial_ends_atstring|nullISO 8601 trial end datetime.
current_period_startstring|nullISO 8601 current period start.
current_period_endstring|nullISO 8601 current period end.
ends_atstring|nullISO 8601 datetime when the subscription ends (set on cancellation).
canceled_atstring|nullISO 8601 cancellation datetime.
paused_atstring|nullISO 8601 pause datetime.
created_atstringISO 8601 creation datetime.
updated_atstringISO 8601 last update datetime.

Invoices

All invoice endpoints are scoped to the token owner.

List invoices

GET /invoices
Returns a paginated list of the token owner’s invoices, newest first. Response 200 OK:
{
  "data": [
    {
      "id": 1,
      "subscription_id": 1,
      "gateway": "stripe",
      "gateway_id": "in_abc123",
      "number": "INV-0001",
      "status": "paid",
      "currency": "USD",
      "subtotal": 1900,
      "tax": 0,
      "discount": 0,
      "total": 1900,
      "refunded_amount": 0,
      "pdf_url": "https://your-app.com/subscribd/invoice/1/pdf",
      "due_at": "2026-04-01T00:00:00+00:00",
      "paid_at": "2026-04-01T00:00:00+00:00",
      "created_at": "2026-04-01T00:00:00+00:00",
      "updated_at": "2026-04-01T00:00:00+00:00"
    }
  ],
  "meta": {
    "total": 1,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1
  }
}

Get an invoice

GET /invoices/{id}
Returns a single invoice. Returns 404 if the invoice does not belong to the token owner.

Invoice object

FieldTypeDescription
idintegerUnique invoice identifier.
subscription_idinteger|nullAssociated subscription ID.
gatewaystringPayment gateway driver name.
gateway_idstring|nullGateway-specific invoice identifier.
numberstring|nullHuman-readable invoice number.
statusstringInvoice status: open, paid, failed, refunded, or disputed.
currencystringISO 4217 currency code.
subtotalintegerSubtotal in minor currency units.
taxintegerTax amount in minor currency units.
discountintegerDiscount amount in minor currency units.
totalintegerTotal amount in minor currency units.
refunded_amountintegerTotal refunded in minor currency units.
pdf_urlstring|nullURL to download the invoice PDF.
due_atstring|nullISO 8601 due date.
paid_atstring|nullISO 8601 payment datetime.
created_atstringISO 8601 creation datetime.
updated_atstringISO 8601 last update datetime.

Payment methods

All payment method endpoints are scoped to the token owner.

List payment methods

GET /payment-methods
Returns all payment methods for the token owner, with default methods listed first. Response 200 OK:
{
  "data": [
    {
      "id": 1,
      "gateway": "stripe",
      "gateway_id": "pm_abc123",
      "type": "card",
      "brand": "visa",
      "last_four": "4242",
      "exp_month": 12,
      "exp_year": 2028,
      "is_default": true,
      "created_at": "2026-04-01T00:00:00+00:00"
    }
  ]
}

Delete a payment method

DELETE /payment-methods/{id}
Detaches the payment method from the gateway and deletes the local record. Response 204 No Content Error responses:
StatusDescription
404Payment method not found or does not belong to the token owner.

Set default payment method

POST /payment-methods/{id}/default
Sets the specified payment method as the default for its gateway. Response 200 OK — returns the updated payment method object. Error responses:
StatusDescription
404Payment method not found or does not belong to the token owner.

Payment method object

FieldTypeDescription
idintegerUnique payment method identifier.
gatewaystringPayment gateway driver name.
gateway_idstring|nullGateway-specific payment method identifier.
typestring|nullPayment method type (e.g., card, paypal).
brandstring|nullCard brand (e.g., visa, mastercard).
last_fourstring|nullLast four digits of the card number.
exp_monthinteger|nullCard expiration month.
exp_yearinteger|nullCard expiration year.
is_defaultbooleanWhether this is the default payment method.
created_atstringISO 8601 creation datetime.

Error responses

All error responses follow a consistent format:
{
  "error": "Human-readable error message."
}
Status codeDescription
401 UnauthorizedMissing, invalid, or expired bearer token.
403 ForbiddenToken lacks the required ability.
404 Not FoundResource not found or not owned by the token owner.
422 Unprocessable EntityValidation failure or business logic error (e.g., invalid state transition).

Issuing tokens programmatically

You can issue tokens in your application code without going through the HTTP endpoint:
use Pixelworxio\Subscribd\Concerns\HasApiTokens;

// On any model using ManagesBilling or HasApiTokens
$result = $user->createApiToken(
    name: 'CI/CD Pipeline',
    abilities: ['subscriptions:read'],
    expiresAt: now()->addYear(),
);

$result['plaintext'];  // store this securely
$result['token'];      // the ApiToken model