> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Close a fiscal year (retained-earnings rollover)

> <Note>Required API key scope: `accounting:close_period`</Note>

Triggers the fiscal year-end close for the authenticated entity. Posts the canonical
closing journal entry that sweeps revenue and expense activity into Retained Earnings
(account 3000), then transitions the last monthly period of the fiscal year to
`year_end_closed` status.

**GAAP / industry pattern (per NetSuite, Acumatica, QuickBooks, AICPA):**
- DR each revenue account (credit balance) -> CR Retained Earnings (account 3000)
- DR Retained Earnings (account 3000) -> CR each COGS + expense account (debit balance)
- Net effect: account 3000 balance += net income for the year

**Preconditions:** every monthly period in the fiscal year must be in `closed` or
`pending_approval` status. Open periods block the close (HTTP 409).

**Idempotent:** re-running for the same `(entity_id, fiscal_year)` returns the
existing closing JE without posting a duplicate. DB partial UNIQUE index on
`(entity_id, source_id) WHERE source_type='fiscal_year_close'` provides race safety.

**Idempotency key:** `fiscal_year_close:<entity_id>:<fiscal_year>`

**Balance sheet impact:** after this endpoint runs, `GET /v1/balance-sheet` returns
`retained_earnings` from the booked account 3000 balance (ledger truth) plus
`current_year_earnings` showing un-closed post-close activity separately.




## OpenAPI

````yaml /openapi.yaml post /fiscal-periods/{id}/close-fiscal-year
openapi: 3.1.0
info:
  title: Arcus ERP Public API
  version: 1.0.0
  description: >
    Arcus ERP public REST API. Designed for external integrations and data
    migration.


    **Authentication.** Bearer token (API key) via the `Authorization` header.

    Format: `Authorization: Bearer ark_live_ent_<code>_<random>` (or
    `ark_test_*` for sandbox).

    API keys are issued per-entity in **Settings > Developers > API Keys**.


    **Entity scoping.** The entity is encoded in the API key prefix; routes are
    flat

    (e.g. `/v1/accounts`, `/v1/orders`, `/v1/products`). A small set of platform
    endpoints

    (migration, reconciliation, events, webhook endpoints, API keys) use the

    `/v1/entities/{entity_id}/...` form -- those are noted in their tags.


    **Key capabilities.**
      - Related-resource hydration via `?expand[]=` (see `x-arcus-expand` on each resource).
      - Cursor-based pagination (`starting_after` / `ending_before` / `limit`).
      - Idempotency via the `Idempotency-Key` header.
      - Webhook events for asynchronous notification.
      - Conditional requests / ETag for cache validation.
servers:
  - url: https://api.arcuserp.com/v1
    description: Arcus ERP API (accepts both live `ark_live_*` and test `ark_test_*` keys)
  - url: https://dev-api.arcuserp.com/v1
    description: >-
      Dev sandbox API (test-only data, accepts `ark_test_*` keys against dev
      RDS)
security: []
paths:
  /fiscal-periods/{id}/close-fiscal-year:
    post:
      tags:
        - Accounting
      summary: Close a fiscal year (retained-earnings rollover)
      description: >
        <Note>Required API key scope: `accounting:close_period`</Note>


        Triggers the fiscal year-end close for the authenticated entity. Posts
        the canonical

        closing journal entry that sweeps revenue and expense activity into
        Retained Earnings

        (account 3000), then transitions the last monthly period of the fiscal
        year to

        `year_end_closed` status.


        **GAAP / industry pattern (per NetSuite, Acumatica, QuickBooks,
        AICPA):**

        - DR each revenue account (credit balance) -> CR Retained Earnings
        (account 3000)

        - DR Retained Earnings (account 3000) -> CR each COGS + expense account
        (debit balance)

        - Net effect: account 3000 balance += net income for the year


        **Preconditions:** every monthly period in the fiscal year must be in
        `closed` or

        `pending_approval` status. Open periods block the close (HTTP 409).


        **Idempotent:** re-running for the same `(entity_id, fiscal_year)`
        returns the

        existing closing JE without posting a duplicate. DB partial UNIQUE index
        on

        `(entity_id, source_id) WHERE source_type='fiscal_year_close'` provides
        race safety.


        **Idempotency key:** `fiscal_year_close:<entity_id>:<fiscal_year>`


        **Balance sheet impact:** after this endpoint runs, `GET
        /v1/balance-sheet` returns

        `retained_earnings` from the booked account 3000 balance (ledger truth)
        plus

        `current_year_earnings` showing un-closed post-close activity
        separately.
      operationId: closeFiscalYearV1
      parameters:
        - name: id
          in: path
          required: true
          description: UUID of the last monthly period of the fiscal year
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fiscal_year
                - fy_start_date
                - fy_end_date
              properties:
                fiscal_year:
                  type: integer
                  description: Fiscal year to close (e.g. 2024)
                  example: 2024
                fy_start_date:
                  type: string
                  format: date
                  description: First day of the fiscal year (e.g. '2024-01-01')
                  example: '2024-01-01'
                fy_end_date:
                  type: string
                  format: date
                  description: Last day of the fiscal year (e.g. '2024-12-31')
                  example: '2024-12-31'
      responses:
        '200':
          description: Fiscal year closed (or idempotent return of existing close JE)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      je_id:
                        type: string
                        format: uuid
                        description: ID of the closing journal entry
                      fiscal_year:
                        type: integer
                      fy_start_date:
                        type: string
                        format: date
                      fy_end_date:
                        type: string
                        format: date
                      net_income_loss:
                        type: number
                        description: >-
                          Net income (positive) or net loss (negative) for the
                          fiscal year
                      retained_earnings_account_id:
                        type: string
                        format: uuid
                      period_id:
                        type: string
                        format: uuid
                        description: ID of the last monthly period (now year_end_closed)
                      balanced:
                        type: boolean
                        description: Always true; closing JE passes assertGLBalance
                      idempotent:
                        type: boolean
                        description: >-
                          True if a prior close JE was returned without
                          re-posting
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '409':
          description: Open periods blocking year-end close
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: close_blocked_by_open_period
                  hint:
                    type: string
                  open_periods:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        period_name:
                          type: string
                        start_date:
                          type: string
                          format: date
                        end_date:
                          type: string
                          format: date
                        status:
                          type: string
        '422':
          $ref: '#/components/responses/Error'
      security:
        - ApiKeyAuth: []
components:
  responses:
    Error:
      description: Error response (400/401/403/404/409/422/429/500)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  schemas:
    ErrorEnvelope:
      type: object
      description: |
        Canonical error response envelope. All API errors use this shape.
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Machine-readable error key
          example: not_found
        code:
          type: string
          description: Machine-readable error code (often same as error)
          example: not_found
        type:
          type: string
          enum:
            - validation_error
            - permission_error
            - not_found
            - conflict
            - rate_limit
            - internal
            - expand_error
            - not_implemented
          example: not_found
        hint:
          type: string
          description: Human-readable one-sentence explanation (English)
          example: >-
            The requested order does not exist or does not belong to this
            entity.
        param:
          type: string
          description: The parameter that caused the error, if applicable
          example: expand[0]
        required:
          type: string
          description: The scope required (only on insufficient_scope errors)
          example: accounts:read
        request_id:
          type: string
          description: >-
            Unique request ID for support tracing (maps to CloudWatch log
            stream)
          example: req_abc123
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: |
        API key issued per entity via Settings > Developers > API Keys.
        Each key carries scopes (e.g. orders:read, products:write).
        Bearer token format: Authorization: Bearer ark_live_ent_<code>_<random>
        Test keys use ark_test_ent_<code>_<random>. Both are issued per entity
        via Settings > Developers > API Keys.

````