Aging Buckets
finance.aging_buckets
Overview
You pass a reference date, open items, and bucket boundaries; we assign each item to a bucket and return totals, counts, and optional item ids per bucket. Mixed currencies are rejected. We never use wall-clock "today" or convert currencies. Do not use this for payment matching or FX conversion.
Silent currency mixing and an implicit "today" make an aging report impossible to reproduce a week later. You pick the date and the ladder, we do the arithmetic in exact decimals and show you anything that fell through.
Reliability
Same input, same report — we read no clock and no rate table. Amounts are exact decimals, rounded HALF_UP to the currency's minor units only when we render them. Anything that matches no bucket lands in unbucketed instead of vanishing. AGING_BUCKETS_OVERLAP — trigger: two of your bucket ranges cover the same day, so an item could belong to either; retryable: no, the same ladder always fails; next: make the ranges disjoint, for example 1-30 then 31-60, and call again. AGING_CURRENCY_MIXED — trigger: your items carry more than one currency code and we never convert; retryable: no; next: split the items per currency and send one call per currency. AGING_ITEM_LIMIT_EXCEEDED — trigger: you sent more than 5,000 items in a single call; retryable: no; next: page your ledger into chunks of 5,000 or fewer and add the bucket totals on your side. AGING_BUCKET_LIMIT_EXCEEDED — trigger: your ladder defines more than 12 buckets; retryable: no; next: merge the narrow ranges until 12 or fewer remain. AMOUNT_INVALID — trigger: an amount, amountPaid, reference date or item date we cannot read, a bucket whose fromDays is greater than its toDays, or an ageBasis or amountBasis outside the accepted values; retryable: no; next: send money as plain decimal strings without thousands separators or exponents, dates as YYYY-MM-DD, and check the message for the item id we named.
Context footprint
Returns bucket totals, percentages, and optional item ids in one reproducible pass. A successful call costs 1 execution credit.
When to use it
When you already know the reference date and the bucket boundaries, and every item is in one currency.
When you still need to convert currencies, allocate payments to invoices, or score collection risk.
Scope
Non-goals
- Does not use the server's current date as "today"
- Does not convert mixed currencies
- Does not match payments to invoices or score collections risk
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| referenceDate | string | Yes | Date every item is aged against as YYYY-MM-DD; never the server clock. |
| items | array | Yes | Up to 5,000 closed open items sharing one ISO 4217 currency, each with amount and a due or issue date. |
| buckets | array | Yes | From 1 through 12 closed bucket definitions evaluated in order with inclusive day bounds that must not overlap. |
| options | object | No | Optional aging controls for which date and amount are bucketed and whether item ids are included; omit it for due_date, outstanding, and item ids included. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| referenceDate | string | Yes | Reference date every item was aged from, echoed so a stored ladder can be re-aged without the original request. |
| currency | string | Yes | Single ISO 4217 code shared by the items, or EUR when the item list is empty. |
| buckets | array | Yes | Your bucket ladder poured with item counts, totals, percent shares, and optional item ids, in the order you sent. |
| total | string | Yes | Grand total across every bucket plus the unbucketed block, as a decimal string. |
| itemCount | integer | Yes | How many items were actually aged after fully paid items were dropped under outstanding. |
| unbucketed | object | Yes | Closed remainder for items that matched no bucket, with count, total, and optional capped item ids. |
| findings | array | Yes | Aging notes such as ladder gaps or excluded settled items; empty when there is nothing to say. |
| _meta | object | No | Settings the report ran with so a stored ladder explains its age basis, amount basis, and bucket count. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"finance.aging_buckets","input":{"referenceDate":"2026-07-25","items":[{"id":"INV-1","dueDate":"2026-06-30","amount":"1200.00","amountPaid":"200.00","currency":"EUR"}],"buckets":[{"label":"current","fromDays":null,"toDays":0},{"label":"1-30","fromDays":1,"toDays":30},{"label":"31-60","fromDays":31,"toDays":60},{"label":"60+","fromDays":61,"toDays":null}],"options":{"ageBasis":"due_date","amountBasis":"outstanding"}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("finance.aging_buckets", {"options":{"ageBasis":"due_date","amountBasis":"outstanding","includeItemIds":true},"items":[{"id":"INV-1","amount":"1200.00","amountPaid":"200.00","currency":"EUR","dueDate":"2026-06-30"}],"referenceDate":"2026-07-25","buckets":[{"label":"current","fromDays":null,"toDays":0},{"label":"1-30","fromDays":1,"toDays":30},{"label":"31-60","fromDays":31,"toDays":60},{"label":"60+","fromDays":61,"toDays":null}]});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| AGING_BUCKETS_OVERLAP | 400 | No | The request payload failed this skill's input validation. |
| AGING_CURRENCY_MIXED | 400 | No | The request payload failed this skill's input validation. |
| AGING_ITEM_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| AGING_BUCKET_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| AMOUNT_INVALID | 400 | No | The request payload failed this skill's input validation. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not use the server's current date as "today"
- Does not convert mixed currencies
- Does not match payments to invoices or score collections risk
- Cross-currency aging without prior conversion
- Payment allocation or cash-application
- Collections scoring or dunning decisions
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "finance.aging_buckets",
"input": {
"options": {
"ageBasis": "due_date",
"amountBasis": "outstanding",
"includeItemIds": true
},
"items": [
{
"id": "INV-1",
"amount": "1200.00",
"amountPaid": "200.00",
"currency": "EUR",
"dueDate": "2026-06-30"
}
],
"referenceDate": "2026-07-25",
"buckets": [
{
"label": "current",
"fromDays": null,
"toDays": 0
},
{
"label": "1-30",
"fromDays": 1,
"toDays": 30
},
{
"label": "31-60",
"fromDays": 31,
"toDays": 60
},
{
"label": "60+",
"fromDays": 61,
"toDays": null
}
]
}
}Examples
One overdue EUR invoice lands in the 1–30 day bucket
{
"options": {
"ageBasis": "due_date",
"amountBasis": "outstanding",
"includeItemIds": true
},
"items": [
{
"id": "INV-1",
"amount": "1200.00",
"amountPaid": "200.00",
"currency": "EUR",
"dueDate": "2026-06-30"
}
],
"referenceDate": "2026-07-25",
"buckets": [
{
"label": "current",
"fromDays": null,
"toDays": 0
},
{
"label": "1-30",
"fromDays": 1,
"toDays": 30
},
{
"label": "31-60",
"fromDays": 31,
"toDays": 60
},
{
"label": "60+",
"fromDays": 61,
"toDays": null
}
]
}{
"referenceDate": "2026-07-25",
"buckets": [
{
"label": "current",
"fromDays": null,
"toDays": 0,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
},
{
"label": "1-30",
"fromDays": 1,
"toDays": 30,
"itemCount": 1,
"total": "1000.00",
"percentOfTotal": "100.00",
"itemIds": [
"INV-1"
]
},
{
"label": "31-60",
"fromDays": 31,
"toDays": 60,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
},
{
"label": "60+",
"fromDays": 61,
"toDays": null,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
}
],
"findings": [],
"itemCount": 1,
"currency": "EUR",
"unbucketed": {
"itemCount": 0,
"itemIds": [],
"total": "0.00"
},
"total": "1000.00",
"_meta": {
"ageBasis": "due_date",
"amountBasis": "outstanding",
"bucketCount": 4
}
}Current bucket catches items not yet due
{
"referenceDate": "2026-07-25",
"buckets": [
{
"label": "current",
"fromDays": null,
"toDays": 0
},
{
"label": "1-30",
"fromDays": 1,
"toDays": 30
},
{
"label": "31-60",
"fromDays": 31,
"toDays": 60
},
{
"label": "60+",
"fromDays": 61,
"toDays": null
}
],
"items": [
{
"amount": "500.00",
"currency": "EUR",
"dueDate": "2026-08-15",
"id": "INV-2"
}
]
}{
"referenceDate": "2026-07-25",
"buckets": [
{
"label": "current",
"fromDays": null,
"toDays": 0,
"itemCount": 1,
"total": "500.00",
"percentOfTotal": "100.00",
"itemIds": [
"INV-2"
]
},
{
"label": "1-30",
"fromDays": 1,
"toDays": 30,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
},
{
"label": "31-60",
"fromDays": 31,
"toDays": 60,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
},
{
"label": "60+",
"fromDays": 61,
"toDays": null,
"itemCount": 0,
"total": "0.00",
"percentOfTotal": "0.00",
"itemIds": []
}
],
"findings": [],
"itemCount": 1,
"currency": "EUR",
"unbucketed": {
"itemCount": 0,
"itemIds": [],
"total": "0.00"
},
"total": "500.00",
"_meta": {
"ageBasis": "due_date",
"amountBasis": "outstanding",
"bucketCount": 4
}
}Recommended use cases
- Build a standard AR aging ladder for a month-end close
- Summarize overdue payables by days past due
- Agent-generated aging reports with reproducible totals
Avoid for
- Cross-currency aging without prior conversion
- Payment allocation or cash-application
- Collections scoring or dunning decisions
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.