medianfi-com Journal Entries API

The Journal Entries API from medianfi-com — 8 operation(s) for journal entries.

Operations 9

GET /companies/{companyId}/journal-entries List journal entries with filters #
POST /companies/{companyId}/journal-entries Create a journal entry with lines (must balance) #
GET /companies/{companyId}/journal-entries/{id} Get a journal entry by ID (includes lines) #
GET /companies/{companyId}/journal-entries/{id}/source Get a journal entry with source drill-down metadata #
POST /companies/{companyId}/journal-entries/{id}/void Void an active posted journal entry #
GET /accountant/companies/{customerId}/transactions/{transactionId}/journal-entries List journal entries linked to a customer transaction #
GET /accountant/companies/{customerId}/journal-entries/{id} Get one customer journal entry by ID (includes lines) #
POST /accountant/companies/{customerId}/journal-entries/{id}/void Accountant void of one active posted journal entry #
POST /accountant/companies/{customerId}/journal-entries/{id}/correct-date Correct the posted date of one active journal entry #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/medianfi-com:medianfi-com-journal-entries-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

medianfi-com-journal-entries-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Median Journal Entries API
  version: 0.1.0
  description: Production-grade Node.js + Express API starter template
  license:
    name: Proprietary
servers:
- url: /api/v1
security: []
tags:
- name: Journal Entries
paths:
  /companies/{companyId}/journal-entries:
    parameters:
    - $ref: '#/components/parameters/companyId'
    get:
      operationId: listJournalEntries
      summary: List journal entries with filters
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      parameters:
      - name: entityId
        in: query
        description: Concrete legal entity. Legacy unscoped rows resolve only to the primary entity.
        schema:
          type: string
          format: uuid
      - name: periodId
        in: query
        schema:
          type: string
          format: uuid
      - name: sourceType
        in: query
        schema:
          type: string
          enum:
          - transaction
          - document
          - adjustment
          - system
      - name: sourceId
        in: query
        schema:
          type: string
      - name: status
        in: query
        description: Omit for posted entries only; `void` returns voided entries. Any other value returns 422.
        schema:
          type: string
          enum:
          - posted
          - void
      - name: dateFrom
        in: query
        schema:
          type: string
          format: date
      - name: dateTo
        in: query
        schema:
          type: string
          format: date
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Paginated list of journal entries
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntriesListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
    post:
      operationId: createJournalEntry
      summary: Create a journal entry with lines (must balance)
      description: Requires company role cfo, admin, or owner.
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateJournalEntryInput'
      responses:
        '201':
          description: Journal entry created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: Validation or business rule error
  /companies/{companyId}/journal-entries/{id}:
    parameters:
    - $ref: '#/components/parameters/companyId'
    - $ref: '#/components/parameters/resourceId'
    get:
      operationId: getJournalEntry
      summary: Get a journal entry by ID (includes lines)
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Journal entry found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Journal entry not found
  /companies/{companyId}/journal-entries/{id}/source:
    parameters:
    - $ref: '#/components/parameters/companyId'
    - $ref: '#/components/parameters/resourceId'
    get:
      operationId: getJournalEntrySource
      summary: Get a journal entry with source drill-down metadata
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Journal entry found with source metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Journal entry not found
  /companies/{companyId}/journal-entries/{id}/void:
    parameters:
    - $ref: '#/components/parameters/companyId'
    - $ref: '#/components/parameters/resourceId'
    post:
      operationId: voidJournalEntry
      summary: Void an active posted journal entry
      description: Requires company role cfo, admin, or owner.
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoidJournalEntryRequest'
      responses:
        '200':
          description: Journal entry voided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedJson'
        '403':
          $ref: '#/components/responses/ForbiddenJson'
        '404':
          $ref: '#/components/responses/NotFoundJson'
        '409':
          description: Journal entry is already void, non-posted, or soft-deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '422':
          description: Journal entry is reconciled or in a filing-locked period (closed periods are voidable)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
  /accountant/companies/{customerId}/transactions/{transactionId}/journal-entries:
    parameters:
    - $ref: '#/components/parameters/customerId'
    - $ref: '#/components/parameters/transactionId'
    get:
      operationId: accountantListTransactionJournalEntries
      summary: List journal entries linked to a customer transaction
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      - serviceApiKey: []
      responses:
        '200':
          description: Journal entries linked by source or line transaction id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntriesListResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedJson'
        '403':
          $ref: '#/components/responses/ForbiddenJson'
  /accountant/companies/{customerId}/journal-entries/{id}:
    parameters:
    - $ref: '#/components/parameters/customerId'
    - $ref: '#/components/parameters/resourceId'
    get:
      operationId: accountantGetJournalEntry
      summary: Get one customer journal entry by ID (includes lines)
      description: Read-back of a single journal entry, e.g. to verify the effect of a void or posting without scanning whole-account activity reports.
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      - serviceApiKey: []
      parameters:
      - name: include
        in: query
        required: false
        schema:
          type: string
          enum:
          - source
        description: Set to `source` to include source drill-down metadata.
      responses:
        '200':
          description: Journal entry found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedJson'
        '403':
          $ref: '#/components/responses/ForbiddenJson'
        '404':
          $ref: '#/components/responses/NotFoundJson'
  /accountant/companies/{customerId}/journal-entries/{id}/void:
    parameters:
    - $ref: '#/components/parameters/customerId'
    - $ref: '#/components/parameters/resourceId'
    post:
      operationId: accountantVoidJournalEntry
      summary: Accountant void of one active posted journal entry
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      - serviceApiKey: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoidJournalEntryRequest'
      responses:
        '200':
          description: Journal entry voided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedJson'
        '403':
          $ref: '#/components/responses/ForbiddenJson'
        '404':
          $ref: '#/components/responses/NotFoundJson'
        '409':
          description: Journal entry is already void, non-posted, or soft-deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '422':
          description: Journal entry is reconciled or in a filing-locked period (closed periods are voidable)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
  /accountant/companies/{customerId}/journal-entries/{id}/correct-date:
    parameters:
    - $ref: '#/components/parameters/customerId'
    - $ref: '#/components/parameters/resourceId'
    post:
      operationId: accountantCorrectJournalEntryDate
      summary: Correct the posted date of one active journal entry
      description: 'Accountant-only date correction for a posted journal entry. The target date must be inside the posted-ledger date domain. Evidence naming the source of the new date is required. Neither the entry''s current period nor the target period may be filing-locked. Requires audit logging and GL recompute. Does not re-date SafeGuard `qbo-sg-9852`.


        A legacy entry (no currency-authoritative line) is moved in place: when a GL period exists for the target date it is bound onto `period_id`, otherwise period_id stays null (do not invent missing months), and a matching source-transaction `occurred_on` is moved in the same write.


        A currency-authoritative entry cannot change its date in place. When it is the posting of a single bank-feed row (`sourceType` `transaction`, `postingKey` `transaction:` or a replacement of it, no line for another row, the row not imported from QuickBooks or mirrored from Stripe), it is voided and replaced in one transaction by an entry with the same lines on the target date, in the target period, with the row''s source and posting key, so the row is never posted a second time on its old date. The response is the replacement (a new `id`, `relatedJeId` pointing at the original); the original keeps `adjustmentReason` `date_correction:->`, which a later rebuild of the row''s posting honors. A correction back to the row''s provider date is recorded as `date_restore:->` instead and releases that pin. The bank row''s `occurred_on` is left as the provider reported it. The target date must fall in an existing GL period, and the row must not be in a completed reconciliation. Any other currency-authoritative entry is refused with 422 `JOURNAL_ENTRY_CURRENCY_AUTHORITY_IMMUTABLE`.'
      tags:
      - Journal Entries
      security:
      - bearerAuth: []
      - serviceApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CorrectJournalEntryDateRequest'
      responses:
        '200':
          description: Journal entry date corrected
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedJson'
        '403':
          $ref: '#/components/responses/ForbiddenJson'
        '404':
          $ref: '#/components/responses/NotFoundJson'
        '409':
          description: 'Journal entry is already void, non-posted, or soft-deleted, or a replacement lost a race (the entry was replaced, its lines or ownership changed, or its bank-feed row went inactive meanwhile)

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '422':
          description: 'Date is out of policy, the entry or its bank-feed row is reconciled, a period involved is filing-locked, no period covers the target date of a replacement, or the entry''s date is currency-authoritative and it is not a single bank-feed posting, or that posting''s bank-feed row no longer exists

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
components:
  schemas:
    CorrectJournalEntryDateRequest:
      type: object
      required:
      - entryDate
      - evidence
      additionalProperties: false
      properties:
        entryDate:
          type: string
          format: date
          description: Target posted date (YYYY-MM-DD) inside the ledger date domain.
        evidence:
          type: string
          minLength: 8
          maxLength: 500
          description: Source of the corrected date (for example a QuickBooks TxnDate).
    CreateJournalEntryInput:
      type: object
      required:
      - entryDate
      - description
      - sourceType
      - lines
      properties:
        entityId:
          type: string
          format: uuid
          description: Concrete legal posting entity; defaults to the company's primary entity.
        entryDate:
          type: string
          format: date
          example: '2024-01-15'
        description:
          type: string
          minLength: 1
          maxLength: 500
        sourceType:
          type: string
          enum:
          - transaction
          - document
          - adjustment
          - system
        sourceId:
          type: string
          format: uuid
        periodId:
          type: string
          format: uuid
        postingKey:
          type: string
          description: 'Optional identity a hand-written replacement can inherit from a voided transaction-sourced entry (`transaction:<uuid>`). Required to keep the engine from re-posting the same transaction (#1975).

            '
          pattern: ^transaction:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
        lines:
          type: array
          minItems: 2
          items:
            type: object
            required:
            - glAccountId
            properties:
              glAccountId:
                type: string
                format: uuid
              debitCents:
                type: integer
                minimum: 0
                default: 0
              creditCents:
                type: integer
                minimum: 0
                default: 0
              memo:
                type: string
                maxLength: 500
              transactionId:
                type: string
                format: uuid
              documentId:
                type: string
                format: uuid
    PaginationMeta:
      type: object
      properties:
        nextCursor:
          type:
          - string
          - 'null'
        hasMore:
          type: boolean
    ApiErrorResponse:
      type: object
      required:
      - success
      - error
      properties:
        success:
          type: boolean
          enum:
          - false
        error:
          $ref: '#/components/schemas/ProblemDetail'
    ProblemDetail:
      type: object
      properties:
        type:
          type: string
        title:
          type: string
          description: 'For application errors, a stable UPPER_SNAKE machine code (for example `NOT_FOUND`, `FORECAST_NOT_FOUND`, `VALIDATION_ERROR`). Clients branch on this, never on `detail` wording. A few transport level responses use a short human title instead.

            '
        status:
          type: integer
        detail:
          type: string
          description: 'Human-readable explanation; correlate a failure with `traceId`. A NOT_FOUND-family 404 names the resource type (for example "Company not found"), not its id. Some legacy messages still include an id (tracked in #2666).

            '
        instance:
          type: string
        traceId:
          type: string
        retryAfter:
          type: integer
          minimum: 0
          description: Whole seconds until retry is allowed for a 429 response
        errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
        extensions:
          type: object
          additionalProperties: true
    JournalEntriesListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: array
          items:
            $ref: '#/components/schemas/JournalEntry'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    VoidJournalEntryRequest:
      type: object
      properties:
        reason:
          type: string
          maxLength: 500
    JournalLine:
      type: object
      required:
      - id
      - journalEntryId
      - glAccountId
      - debitCents
      - creditCents
      - currency
      - minorUnitExponent
      - ledgerDebitCents
      - ledgerCreditCents
      - ledgerCurrency
      - ledgerMinorUnitExponent
      properties:
        id:
          type: string
          format: uuid
        journalEntryId:
          type: string
          format: uuid
        glAccountId:
          type: string
          format: uuid
        classId:
          type:
          - string
          - 'null'
          format: uuid
          description: 'Line-level class dimension for downstream reporting. Accountant-portal responses always carry this field. Customer-facing responses do not carry it on journal lines.

            '
        className:
          type:
          - string
          - 'null'
          description: 'Name of the class in `classId`, resolved from the company''s class list. Accountant-portal responses only, present when the class is known.

            '
        classStatus:
          type: string
          enum:
          - draft
          - active
          - inactive
          description: 'Status of the class in `classId`. Accountant-portal responses only; never returned to the customer app.

            '
        debitCents:
          type: integer
        creditCents:
          type: integer
        currency:
          type: string
        memo:
          type:
          - string
          - 'null'
        transactionId:
          type:
          - string
          - 'null'
          format: uuid
        documentId:
          type:
          - string
          - 'null'
          format: uuid
    JournalEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        companyId:
          type: string
          format: uuid
        entityId:
          type:
          - string
          - 'null'
          format: uuid
          description: Concrete legal posting entity. Null is legacy data and resolves to the primary entity.
        entryDate:
          type: string
          format: date
        description:
          type: string
        sourceType:
          type: string
          enum:
          - transaction
          - document
          - adjustment
          - system
        sourceId:
          type:
          - string
          - 'null'
          format: uuid
        periodId:
          type:
          - string
          - 'null'
          format: uuid
        status:
          type: string
          enum:
          - posted
          - void
        createdByUserId:
          type:
          - string
          - 'null'
          format: uuid
        createdAt:
          type: string
          format: date-time
        deletedAt:
          type:
          - string
          - 'null'
          format: date-time
        deletedByUserId:
          type:
          - string
          - 'null'
          format: uuid
        relatedJeId:
          type:
          - string
          - 'null'
          format: uuid
        adjustmentReason:
          type:
          - string
          - 'null'
        isClosingEntry:
          type: boolean
          description: A period-end closing entry (a system entry keyed on one of the company's periods). Reports leave these out of the P&L.
        sourceLabel:
          type: string
        sourceLink:
          oneOf:
          - type: object
            properties:
              kind:
                type: string
              id:
                type: string
          - type: 'null'
        lines:
          type: array
          items:
            $ref: '#/components/schemas/JournalLine'
    JournalEntryResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/JournalEntry'
  responses:
    ForbiddenJson:
      description: Insufficient permissions or not a company member
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
    UnauthorizedJson:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
    Forbidden:
      description: Insufficient permissions or not a company member
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetail'
    Unauthorized:
      description: Authentication required
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetail'
    NotFoundJson:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
    ValidationError:
      description: Request failed validation
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetail'
  parameters:
    customerId:
      name: customerId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    resourceId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    companyId:
      name: companyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    transactionId:
      name: transactionId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    cursor:
      name: cursor
      in: query
      schema:
        type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    serviceApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Persistent credential for the configured cloud-bookkeeping service principal. The key must carry accountant:* and is accepted only on accountant operations that explicitly list this scheme. Ordinary API keys, and service keys presented to bearer-only operations, are denied.

        '
    refreshSessionCookie:
      type: apiKey
      in: cookie
      name: __Secure-median_refresh
      description: 'Exact production HttpOnly refresh-session cookie. Local non-secure development uses median_refresh.

        '
    passkeyEnrollmentGrantCookie:
      type: apiKey
      in: cookie
      name: __Secure-median_passkey_enrollment
      description: 'Exact production HttpOnly passkey-enrollment grant cookie. Local non-secure development uses median_passkey_enrollment.

        '
    webAuthnCeremonyCookie:
      type: apiKey
      in: cookie
      name: __Secure-median_webauthn
      description: 'Exact production HttpOnly WebAuthn ceremony cookie. Local non-secure development uses median_webauthn.

        '
x-median-quarantined-authorities:
  decision: ADR-0001-model-a
  runtimeStatus: unmounted
  description: 'Migrations 267-272 modeled legal entities as separate company tenants. These paths are not runtime API authority while their preserved accounting math is re-keyed to legal entities inside one company workspace.

    '
  paths:
  - /companies/{companyId}/consolidation-groups
  - /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/report-rollups
  - /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/report-rollups/{rollupId}
  - /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/translation-adjustments
  - /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/translation-adjustments/{adjustmentId}
x-median-demo-parity-customer-surface:
  runtimeStatus: mounted-lifecycle-only
  description: 'v2 customer-app capabilities available to JWT-authenticated members of any active or test company — the v2 app is the product, not a rollout cohort, so no allowlist applies to the markerless path. Production customer calls omit X-Median-App-Surface. Test-company calls may carry X-Median-App-Surface: qa to select the QA bundle; that marker branch alone still requires membership in CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS. An API key, unknown marker, malformed company id, inactive company, missing gate dependency, or failed company lookup receives the standard hidden-resource 404 contract. The held surfaces riding these prefixes (AR/AP, Forecast, Insights) carry their own additional allowlist gate — see x-median-forecast-hold and x-median-ar-ap-hold.

    '
  headerContract: '#/components/parameters/demoParityCustomerAppSurface'
  routePrefixes:
  - /companies/{companyId}/ar
  - /companies/{companyId}/ap
  - /companies/{companyId}/customers
  - /companies/{companyId}/vendors
  - /companies/{companyId}/contracts
  - /companies/{companyId}/receivables
  - /companies/{companyId}/entities
  - /companies/{companyId}/currencies
  - /companies/{companyId}/reporting-scopes
  - /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/rollups
  - /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/translation-adjustments
  - /companies/{companyId}/presentation-report-snapshots
  - /companies/{companyId}/reporting-trial-balances
  - /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/trial-balances
  - /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/intercompany
  - /companies/{companyId}/weekly-email
  - /companies/{companyId}/forecast
  - /companies/{companyId}/books
  - /companies/{companyId}/sub-ledgers
x-median-forecast-hold:
  runtimeStatus: mounted-only-with-server-gate
  description: 'Forecast is held. Beyond the lifecycle-only customer surface above, every operation under the prefix below also requires the company to appear in the held-surface allowlist CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS — the one operator-set list that opens AR/AP, Forecast and Insights (the legacy CUSTOMER_APP_FORECAST_COMPANY_IDS variable is no longer consulted). It is empty by default, so the v2 rollout alone never exposes Forecast. A company off the list receives the same hidden-resource 404 contract as one for which the surface does not exist. The auth-context featureFlags key `forecast` mirrors this verdict for the customer app shell.

    '
  featureFlag: forecast
  routePrefixes:
  - /companies/{companyId}/forecast
x-median-ar-ap-hold:
  runtimeStatus: mounted-only-with-server-gate
  description: 'Receivables/Payables are held behind TWO required authorities. Beyond the lifecycle-only customer surface above, every operation under the prefixes below requires the company to appear in the held-surface allowlist CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS (requireDemoParityAllowlistedCompany) AND to satisfy the company_subledger_flags conversion chain (requireArApItemsEnabled — the same authority the public /pay page, aging and the accountant portal consult); failing either answers the opaque 404. The auth-context featureFlags key `arApItems` mirrors exactly that conjunction, resolved server-side, so the customer shell hides the Receivables/Payables nav and pages instead of offering menu entries these routes 404. Default is FALSE for every company while the list is empty or the conversion has not run.

    '
  featureFlag: arApItems
  routePrefixes:
  - /companies/{companyId}/ar
  - /companies/{companyId}/ap
  - /companies/{companyId}/customers
  - /companies/{companyId}/vendors
  - /companies/{companyId}/contracts
  - /companies/{companyId}/receivables
x-median-demo-parity-qa-only:
  runtimeStatus: mounted-only-with-server-gate
  description: 'Strict test-company-only routes. Every interactive call requires X-Median-App-Surface: qa in addition to JWT authentication, the server allowlist, and persisted client_status=test. A copied header, API key, active company, missing gate dependency, or failed company lookup receives the standard hidden-resource 404 contract.

    '
  headerContract: '#/components/parameters/qaAppSurface'
  routePrefixes:
  - /companies/{companyId}/gl-accounts/{id}/monetary-classification
  - /companies/{companyId}/fx-account-roles
  - /companies/{companyId}/gl-periods/{periodId}/fx-revaluation
  - /accountant/companies/{customerId}/gl-accounts/{id}/monetary-classification
  - /accountant/companies/{customerId}/fx-account-roles
  - /accountant/companies/{customerId}/gl-periods/{periodId}/fx-revaluation
  - /accountant/companies/{customerId}/ar/{id}
  - /accountant/companies/{customerId}/ap/{id}
x-median-onboarding-v3-surface:
  runtimeStatus: promoted
  promotionFlag: ONBOARDING_V3_QA_ONLY=false
  description: 'Promoted. The deployment this contract describes runs ONBOARDING_V3_QA_ONLY=false, so none of the onboarding-v3 auth, pricing, or setup paths below requires X-Median-App-Surface: qa. An unmarked request is served and reaches ordinary authentication, so an unauthenticated call now answers 401 rather than the hidden-resource 404. Mounting is still conditional: with ONBOARDING_V3_ENABLED false every path below returns the hidden-resource 404 before body parsing or authentication. The header stays defined and is still honoured, because a deployment left at ONBOARDING_V3_QA_ONLY=true keeps requiring it on every path below and 404s unmarked traffic; setting that flag back to true is the rollback. On either setting the header is a pre-company bundle marker, never tenant authorization.

    '
  headerContract: '#/components/parameters/onboardingV3QaSurface'
  routePaths:
  - /auth/otp/start
  - /auth/otp/verify
  - /auth/passkeys/register/options
  - /auth/passkeys/register/verify
  - /auth/passkeys/assert/options
  - /auth/passkeys/assert/verify
  - /onboarding/v3/quote
  - /onboarding/v3/offer/resolve
  - /onboarding/v3/flow
  - /onboarding/v3/setup-intent
  - /onboarding/v3/subscribe
  - /onboarding/v3/test-account-activation
  - /onboarding/v3/checkout-session
  - /onboarding/v3/checkout-session/complete
  - /onboarding/v3/setup-state
  - /onboarding/v3/delegate-invites
  - /onboarding/v3/delegate-invites/{inviteId}
  - /onboarding/v3/delegate/resolve
  - /onboarding/v3/delegate
  - /onboarding/v3/task-context
  - /onboarding/v3/delegate/task-context
  - /onboarding/v3/banks/plaid/link-token
  - /onboarding/v3/banks/plaid/exchange
  - /onboarding/v3/banks/commit
  - /onboarding/v3/documents/google-workspace/auth-url
  - /onboarding/v3/documents/google-workspace/callback
  - /onboarding/v3/books/quickbooks/auth-url
  - /onboarding/v3/books/quickbooks/callback
  - /onboarding/v3/books/xero/auth-url
  - /onboarding/v3/books/xero/callback
  - /onboarding/v3/welcome-call
  - /onboarding/v3/welcome-call/embed
  - /onboarding/v3/books-progress
  - /onboarding/v3/books/open
  - /companies/{companyId}/onboarding/v3/questions
  - /companies/{companyId}/onboarding/v3/questions/{questionId}/answer
  - /companies/{companyId}/onboarding/v3/review-items