FinzBooksDevelopers

Recurring schedules

Auto-generate invoices / bills / expenses / journal entries on a cron-like cadence. Subscription billing, monthly retainers, quarterly rent invoices, yearly insurance renewals — all configured once and fired by the FinzBooks scheduler.

A schedule has an entity_type (what document it emits), a frequency (DAILY / WEEKLY / MONTHLY / QUARTERLY / YEARLY) with an interval multiplier (so “every 2 weeks” is WEEKLY with interval: 2), and a template — either a reference to an existing document to clone, or an inline JSON payload to build from scratch. The background job wakes up daily at 09:00 IST, finds schedules whose next_run_at has passed, generates the documents, and advances next_run_at by one interval.

Resource shape

{
  "recurring_id":          "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
  "entity_type":           "INVOICE",
  "name":                  "Acme monthly retainer",
  "frequency":             "MONTHLY",
  "interval":              1,
  "start_date":            "2026-06-01",
  "end_date":              null,
  "next_run_at":           "2026-06-01T03:30:00Z",
  "last_run_at":           null,
  "is_active":             true,
  "template_entity_id":    "inv_template_abc",
  "template_payload":      null,
  "run_count":             0,
  "max_runs":              null,
  "auto_charge":           false,
  "created_time":          "2026-05-12T07:00:00Z",
  "last_modified_time":    "2026-05-12T07:00:00Z"
}

Common request headers

Authorization:   Bearer aibooks_pat_XXXX...
X-AIBooks-Org:   org_8f9a1c2d
Content-Type:    application/json
GET/recurringList recurring schedulesAIBooks.recurring.READ

Filter by ?entity_type=INVOICE or ?is_active=true. Ordered by next_run_atascending — soonest-firing first, which is what an “upcoming runs” widget needs.

Sample request

GET /api/public/v1/recurring?entity_type=INVOICE&is_active=true&per_page=25 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "success",
  "recurring": [
    {
      "recurring_id":       "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
      "entity_type":        "INVOICE",
      "name":               "Acme monthly retainer",
      "frequency":          "MONTHLY",
      "interval":           1,
      "start_date":         "2026-06-01",
      "end_date":           null,
      "next_run_at":        "2026-06-01T03:30:00Z",
      "last_run_at":        null,
      "is_active":          true,
      "template_entity_id": "inv_template_abc",
      "template_payload":   null,
      "run_count":          0,
      "max_runs":           null,
      "auto_charge":        false,
      "created_time":       "2026-05-12T07:00:00Z",
      "last_modified_time": "2026-05-12T07:00:00Z"
    },
    {
      "recurring_id":       "rec_5e7f9a1b2c3d4e5f6a7b8c9d0e1f2a3b",
      "entity_type":        "INVOICE",
      "name":               "Contoso quarterly licence",
      "frequency":          "QUARTERLY",
      "interval":           1,
      "start_date":         "2026-04-01",
      "end_date":           "2027-03-31",
      "next_run_at":        "2026-07-01T03:30:00Z",
      "last_run_at":        "2026-04-01T03:30:00Z",
      "is_active":          true,
      "template_entity_id": "inv_template_def",
      "template_payload":   null,
      "run_count":          1,
      "max_runs":           4,
      "auto_charge":        true,
      "created_time":       "2026-03-20T10:14:00Z",
      "last_modified_time": "2026-04-01T03:30:02Z"
    }
  ],
  "page_context": { "page": 1, "per_page": 25, "has_more_page": false }
}
GET/recurring/{schedule_id}Get one scheduleAIBooks.recurring.READ

Standard single-resource fetch. Returns the same shape as the list row.

Sample request

GET /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "success",
  "recurring": {
    "recurring_id":       "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "entity_type":        "INVOICE",
    "name":               "Acme monthly retainer",
    "frequency":          "MONTHLY",
    "interval":           1,
    "start_date":         "2026-06-01",
    "end_date":           null,
    "next_run_at":        "2026-06-01T03:30:00Z",
    "last_run_at":        null,
    "is_active":          true,
    "template_entity_id": "inv_template_abc",
    "template_payload":   null,
    "run_count":          0,
    "max_runs":           null,
    "auto_charge":        false,
    "created_time":       "2026-05-12T07:00:00Z",
    "last_modified_time": "2026-05-12T07:00:00Z"
  }
}
POST/recurringCreate a scheduleAIBooks.recurring.CREATE

Pass exactly one of template_entity_id (clone an existing document on every run) or template_payload (inline JSON template). The clone path is by far the most common — you build a prototype invoice in the dashboard, save it, then attach a schedule that clones it monthly.

Body

FieldNotes
entity_typeWhat kind of document to emit.
nameHuman-friendly label shown in the dashboard.
frequencyDAILY / WEEKLY / MONTHLY / QUARTERLY / YEARLY.
intervalEvery N frequency units. Default 1.
start_dateFirst run fires at 09:00 IST on this date.
end_dateOptional cap. After this date the schedule auto-pauses.
template_entity_idID of an existing document to clone.
template_payloadInline JSON — same shape as the resource's POST body.
max_runsStop after N successful runs.
auto_chargeInvoice schedules only: auto-create a online payment link on each new invoice and embed the URL.
is_activeDefault true. Create paused with false if you want to review before activating.

Sample request — clone an existing invoice

POST /api/public/v1/recurring HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json

{
  "entity_type":        "INVOICE",
  "name":               "Acme monthly retainer",
  "frequency":          "MONTHLY",
  "interval":           1,
  "start_date":         "2026-06-01",
  "end_date":           null,
  "template_entity_id": "inv_template_abc",
  "max_runs":           null,
  "auto_charge":        false,
  "is_active":          true
}

Sample request — inline payload

POST /api/public/v1/recurring HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json

{
  "entity_type": "INVOICE",
  "name":        "Office rent — landlord Mehta",
  "frequency":   "MONTHLY",
  "interval":    1,
  "start_date":  "2026-06-01",
  "template_payload": {
    "customer_id": "cnt_landlord_mehta",
    "branch_id":   "br_hyd_01",
    "currency":    "INR",
    "place_of_supply": "TS",
    "line_items": [
      {
        "item_id":  "itm_office_rent",
        "quantity": 1,
        "rate":     85000,
        "tax_id":   "tax_gst_18"
      }
    ],
    "notes": "Auto-generated monthly rent invoice."
  }
}

Sample response

HTTP/1.1 201 Created
Content-Type: application/json

{
  "code":    0,
  "message": "The recurring schedule has been created.",
  "recurring": {
    "recurring_id":       "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "entity_type":        "INVOICE",
    "name":               "Acme monthly retainer",
    "frequency":          "MONTHLY",
    "interval":           1,
    "start_date":         "2026-06-01",
    "end_date":           null,
    "next_run_at":        "2026-06-01T03:30:00Z",
    "last_run_at":        null,
    "is_active":          true,
    "template_entity_id": "inv_template_abc",
    "template_payload":   null,
    "run_count":          0,
    "max_runs":           null,
    "auto_charge":        false,
    "created_time":       "2026-05-12T07:00:00Z",
    "last_modified_time": "2026-05-12T07:00:00Z"
  }
}
PUT/recurring/{schedule_id}Update a scheduleAIBooks.recurring.UPDATE

Partial update. Pass only the fields you want changed. Updating frequency / interval /start_date recomputes next_run_at on the next scheduler tick — you can also set next_run_at explicitly to skip ahead.

Sample request

PUT /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json

{
  "name":         "Acme monthly retainer (₹95k from FY27)",
  "max_runs":     24,
  "next_run_at":  "2026-07-01T03:30:00Z"
}

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "The recurring schedule has been updated.",
  "recurring": {
    "recurring_id":       "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "entity_type":        "INVOICE",
    "name":               "Acme monthly retainer (₹95k from FY27)",
    "frequency":          "MONTHLY",
    "interval":           1,
    "start_date":         "2026-06-01",
    "end_date":           null,
    "next_run_at":        "2026-07-01T03:30:00Z",
    "last_run_at":        null,
    "is_active":          true,
    "template_entity_id": "inv_template_abc",
    "template_payload":   null,
    "run_count":          0,
    "max_runs":           24,
    "auto_charge":        false,
    "created_time":       "2026-05-12T07:00:00Z",
    "last_modified_time": "2026-05-12T07:42:11Z"
  }
}
POST/recurring/{schedule_id}/runFire immediatelyAIBooks.recurring.UPDATE

Manual trigger. Generates one document using the exact same code path the background scheduler uses, increments run_count, advances next_run_at by one interval, and sets last_run_atto now. Returns the new document's ID so the integrator can fetch the full resource.

Sample request

POST /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02/run HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":                0,
  "message":             "The schedule has fired.",
  "generated_entity_id": "inv_new_42a91f",
  "recurring": {
    "recurring_id":       "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "entity_type":        "INVOICE",
    "name":               "Acme monthly retainer (₹95k from FY27)",
    "frequency":          "MONTHLY",
    "interval":           1,
    "start_date":         "2026-06-01",
    "end_date":           null,
    "next_run_at":        "2026-08-01T03:30:00Z",
    "last_run_at":        "2026-05-12T07:45:00Z",
    "is_active":          true,
    "template_entity_id": "inv_template_abc",
    "template_payload":   null,
    "run_count":          1,
    "max_runs":           24,
    "auto_charge":        false,
    "created_time":       "2026-05-12T07:00:00Z",
    "last_modified_time": "2026-05-12T07:45:00Z"
  }
}

If the template document was deleted between schedule create and the manual run, the call fails with validation.invalid_value and the schedule is not advanced.

POST/recurring/{schedule_id}/pausePauseAIBooks.recurring.UPDATE

Sample request

POST /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02/pause HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "The recurring schedule has been paused.",
  "recurring": {
    "recurring_id": "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "is_active":    false,
    "next_run_at":  "2026-08-01T03:30:00Z"
    /* …rest of schedule fields */
  }
}
POST/recurring/{schedule_id}/resumeResumeAIBooks.recurring.UPDATE

Convenience setters for is_active. Paused schedules are skipped by the scheduler but retain their next_run_at — resuming picks back up where the timeline left off (or shifts forward if next_run_at is already in the past).

Sample request

POST /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02/resume HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "The recurring schedule has been resumed.",
  "recurring": {
    "recurring_id": "rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
    "is_active":    true,
    "next_run_at":  "2026-08-01T03:30:00Z"
    /* …rest of schedule fields */
  }
}
DELETE/recurring/{schedule_id}Delete a scheduleAIBooks.recurring.DELETE

Hard delete. Documents already generated by past runs are not affected.

Sample request

DELETE /api/public/v1/recurring/rec_8c3f1a9b40b14e3aaf2d7e91a76c1f02 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d

Sample response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "code":    0,
  "message": "The recurring schedule has been deleted."
}

Errors

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "code":    "validation.required_field",
  "message": "Either 'template_entity_id' or 'template_payload' must be supplied.",
  "field":   "template_entity_id"
}
CodeHTTPWhen
validation.required_field400Neither template_entity_id nor template_payload was supplied at create time.
validation.invalid_value400Both template_entity_id and template_payload set, OR unknown entity_type / frequency, OR the template was deleted before a manual run.
not_found.resource404Schedule ID not found in this org.

Worked example — monthly retainer invoice

  1. Create a template invoice manually in the dashboard (e.g. INV-RETAINER-TEMPLATE). Note its ID.
  2. POST /recurring with entity_type: INVOICE, frequency: MONTHLY, start_date: 2026-06-01, and template_entity_id: inv_template_id.
  3. On the 1st of every month at 09:00 IST the scheduler clones the template, generates a new invoice number, stamps today as the invoice date, and emits an INVOICE_CREATED webhook.
  4. If the customer pays directly through a Razorpay link (when auto_charge: true), a webhook fires and the invoice flips to PAID without manual reconciliation.