Skip to main content

Mark Missing Payments

Flag a tenant as having unpaid invoices. The dashboard block engages automatically two weeks after the flag is set.

The endpoint is idempotent: calling it on an already-flagged tenant returns 200 with the existing timestamp. The timestamp is never refreshed, and the unpaid-invoices notification is never re-dispatched. The Stripe-customer check only applies on fresh marks, so a tenant flagged earlier whose Stripe customer is later removed still returns the cached 200 on retry.

If a force-refresh of the timestamp and a re-notification are needed, coordinate with engineering. The Internal API does not expose that.


Endpoint

POST /internal-api/v1/tenants/{tenant}/missing-payments

Required ability

tenants:write

Path parameter

ParameterTypeDescription
tenantintegerThe customer tenant id. The platform tenant (1) is rejected with 403.

Body

Empty.


What this does

  1. Verifies the tenant has a Stripe customer attached. Refuses with 400 (tenant_has_no_stripe_customer) otherwise.
  2. Writes the current timestamp to the tenant's sign_up_intents.has_unpaid_invoices_at column.
  3. Dispatches the unpaid-invoices notification once, the first time the tenant is flagged.

The dashboard block (the customer-visible "your store is paused" page) does not engage until two weeks after the timestamp. The 2-week grace window is fixed by the platform.


Response

Success (200 OK)

Fresh mark:

{
"data": {
"tenant_id": "2",
"has_unpaid_invoices_at": "2026-05-05T12:21:32.000000Z",
"dashboard_blocked": false,
"missing_payment_limit_date": "2026-05-19T12:21:32.000000Z"
}
}

Idempotent re-mark (returns the existing timestamp, nothing is refreshed):

{
"data": {
"tenant_id": "2",
"has_unpaid_invoices_at": "2026-05-05T12:21:32.000000Z",
"dashboard_blocked": false,
"missing_payment_limit_date": "2026-05-19T12:21:32.000000Z"
}
}

Mark older than two weeks (block engaged):

{
"data": {
"tenant_id": "2",
"has_unpaid_invoices_at": "2026-04-01T12:00:00.000000Z",
"dashboard_blocked": true,
"missing_payment_limit_date": "2026-04-15T12:00:00.000000Z"
}
}
FieldTypeDescription
tenant_idstringTenant id
has_unpaid_invoices_atdatetimeThe moment the tenant was flagged. Idempotent calls return the existing value.
dashboard_blockedboolWhether the dashboard block is engaged right now (computed from current time)
missing_payment_limit_datedatetimehas_unpaid_invoices_at + 2 weeks. The dashboard block engages once now() passes this.

Examples

Mark a customer tenant

curl -X POST "https://app.publica.la/internal-api/v1/tenants/2/missing-payments" \
-H "Authorization: Bearer <id>|pla_int_<secret>" \
-H "Accept: application/json"

Mark already-marked (no-op)

The same call against an already-marked tenant returns 200 with the existing timestamp. Safe to retry.

curl -X POST "https://app.publica.la/internal-api/v1/tenants/2/missing-payments" \
-H "Authorization: Bearer <id>|pla_int_<secret>" \
-H "Accept: application/json"

Error Handling

400 Tenant has no Stripe customer

{
"error": {
"code": "tenant_has_no_stripe_customer",
"message": "Tenant has no stripe customer."
}
}

The tenant has no Stripe customer attached. The same code covers tenants that have never had a payment-intent record created.

401 Unauthenticated

Missing Bearer, only X-User-Token sent, or revoked token.

403 Missing ability

The token does not carry tenants:write.

{
"message": "Invalid ability provided."
}

403 Root tenant

{
"message": "Operations on the root tenant are not allowed via internal-api."
}

The path resolved to the platform tenant. Use a customer tenant id.

404 Tenant not found

{
"message": "Not found"
}

{tenant} does not resolve to any tenant. Also returned when the request resolves to a non-platform host, or when X-Farfalla-Tenant-Id is set to a non-platform tenant id on app.publica.la.

429 Rate limited

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>

{
"message": "Too Many Attempts."
}

The token has spent its 60 RPM budget.


See also

X

Graph View