openapi: 3.1.0
info:
  title: FileMaker Data API (independent reference)
  version: 1.0.0
  summary: REST access to FileMaker Server hosted databases.
  description: |
    An independently written OpenAPI description of the Claris FileMaker
    Data API, version `v1`, covering sessions, record operations, finds,
    and container uploads.

    **Read this first**

    - Every response uses the same envelope: a `response` object and a
      `messages` array. Message `code` values are **strings**; success is
      `"0"`.
    - Log in with HTTP Basic credentials to obtain a bearer token. Every
      other call sends `Authorization: Bearer {token}`. Tokens expire after
      15 minutes of inactivity.
    - Logout is the one endpoint that carries the token in the URL path
      instead of a header.
    - A find that matches nothing returns HTTP 404 with FileMaker code
      `401`. Treat it as an empty result, not a failure.
    - The authenticating account's privilege set must include the `fmrest`
      extended privilege.

    This document is a portfolio work sample and is not affiliated with or
    endorsed by Claris International Inc.
  contact:
    name: Justin Arndt
    url: https://j-arndt.github.io
servers:
  - url: https://{host}/fmi/data/v1
    description: >-
      FileMaker Server. Replace the version segment with v2 or vLatest as
      needed.
    variables:
      host:
        default: fms.example.com
        description: Fully qualified host name of the FileMaker Server.
tags:
  - name: Sessions
    description: Log in to obtain a bearer token; log out to release it.
  - name: Records
    description: Create, read, list, edit, and delete records through a layout.
  - name: Finds
    description: Query records with FileMaker find grammar.
  - name: Containers
    description: Upload files into container fields.
security:
  - bearerAuth: []
paths:
  /databases/{database}/sessions:
    post:
      operationId: login
      tags:
        - Sessions
      summary: Log in (create a session)
      description: |
        Exchanges HTTP Basic credentials for a session token. The token is
        returned in the response body and in the `X-FM-Data-Access-Token`
        header. Send an empty JSON object as the body when no external data
        sources are involved.
      security:
        - basicAuth: []
      parameters:
        - $ref: '#/components/parameters/Database'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: >-
                Empty for single-file authentication. May carry `fmDataSource`
                credentials for external FileMaker data sources.
              properties:
                fmDataSource:
                  type: array
                  description: >-
                    Credentials for external FileMaker data sources referenced
                    by the file.
                  items:
                    type: object
                    properties:
                      database:
                        type: string
                      username:
                        type: string
                      password:
                        type: string
            example: {}
      responses:
        '200':
          description: Session created.
          headers:
            X-FM-Data-Access-Token:
              description: The session token, duplicated from the response body.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponse'
        '401':
          description: >-
            Invalid account name or password (FileMaker code 212), or the
            privilege set lacks the fmrest extended privilege.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /databases/{database}/sessions/{token}:
    delete:
      operationId: logout
      tags:
        - Sessions
      summary: Log out (delete a session)
      description: |
        Invalidates a session token. Unusually for this API, the token
        travels in the URL path and no Authorization header is sent.
      security: []
      parameters:
        - $ref: '#/components/parameters/Database'
        - name: token
          in: path
          required: true
          description: The session token to invalidate.
          schema:
            type: string
      responses:
        '200':
          description: Session deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        '401':
          description: Token not found or already invalid (FileMaker code 952).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /databases/{database}/layouts/{layout}/records:
    get:
      operationId: listRecords
      tags:
        - Records
      summary: List records
      description: |
        Returns a range of records in the layout's context. `_offset` starts
        at 1. `_sort` is a JSON array passed as a URL-encoded string.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
        - name: _offset
          in: query
          description: One-based index of the first record to return. Defaults to 1.
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: _limit
          in: query
          description: Maximum number of records to return. Defaults to 100.
          schema:
            type: integer
            minimum: 1
            default: 100
        - name: _sort
          in: query
          description: >-
            URL-encoded JSON array of sort specifications, for example:
            [{"fieldName":"Name","sortOrder":"ascend"}]
          schema:
            type: string
      responses:
        '200':
          description: Records returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordsResponse'
        '401':
          description: Missing or expired token (FileMaker code 952).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Database or layout not found (FileMaker codes 100, 105).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createRecord
      tags:
        - Records
      summary: Create a record
      description: |
        Creates one record from a `fieldData` object. Only fields present on
        the layout can be set (a missing field raises FileMaker code 102).
        Container fields cannot be set here; use the container upload call.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRecordRequest'
            example:
              fieldData:
                Name: Karl Fischer titrant, 5L
                SKU: KF-5000
                UnitPrice: 412.5
                Status: Active
      responses:
        '200':
          description: Record created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateRecordResponse'
        '400':
          description: >-
            Validation failure, such as a unique field rejecting a duplicate
            (FileMaker code 504).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or expired token (FileMaker code 952).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /databases/{database}/layouts/{layout}/records/{recordId}:
    get:
      operationId: getRecord
      tags:
        - Records
      summary: Get one record
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
        - $ref: '#/components/parameters/RecordId'
      responses:
        '200':
          description: The record, as a single-element data array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordsResponse'
        '404':
          description: Record not found (FileMaker code 101).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      operationId: editRecord
      tags:
        - Records
      summary: Edit a record
      description: |
        Updates the fields present in `fieldData`, leaving all others
        untouched. Include the `modId` from a prior read to enable
        optimistic locking: the edit is rejected with FileMaker code 306 if
        the record changed after that read.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
        - $ref: '#/components/parameters/RecordId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EditRecordRequest'
            example:
              fieldData:
                Status: Discontinued
              modId: '7'
      responses:
        '200':
          description: Record updated; the new modification id is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditRecordResponse'
        '400':
          description: Modification id mismatch (FileMaker code 306) or validation failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Record not found (FileMaker code 101).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      operationId: deleteRecord
      tags:
        - Records
      summary: Delete a record
      description: |
        Deletes the record immediately. Cascading deletion of related
        records follows the file's relationship settings, exactly as it
        would in FileMaker Pro.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
        - $ref: '#/components/parameters/RecordId'
      responses:
        '200':
          description: Record deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        '404':
          description: Record not found (FileMaker code 101).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /databases/{database}/layouts/{layout}/_find:
    post:
      operationId: findRecords
      tags:
        - Finds
      summary: Find records
      description: |
        Runs a find using FileMaker query grammar. Fields inside one query
        object are ANDed; multiple query objects are ORed; a query object
        with `"omit": "true"` excludes its matches.

        Criteria strings accept FileMaker find operators, for example
        `>=100`, `Karl*`, `==exact match`, and `2025-01-01...2025-12-31`.

        **Empty result behavior:** no matches returns HTTP 404 with
        FileMaker code 401. Treat that combination as a found set of zero.

        Note that `offset` and `limit` are strings in this request body,
        unlike the integer query parameters on the list call.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FindRequest'
            example:
              query:
                - Status: Active
                  UnitPrice: '>=100'
              sort:
                - fieldName: UnitPrice
                  sortOrder: descend
              offset: '1'
              limit: '25'
      responses:
        '200':
          description: Matching records.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordsResponse'
        '400':
          description: >-
            Malformed query, or a criteria field missing from the layout
            (FileMaker code 102).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >-
            No records match (FileMaker code 401). An empty result, not a
            failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /databases/{database}/layouts/{layout}/records/{recordId}/containers/{fieldName}/{fieldRepetition}:
    post:
      operationId: uploadToContainer
      tags:
        - Containers
      summary: Upload a file into a container field
      description: |
        Uploads one file into a container field as multipart form data. The
        form part must be named `upload`. Use repetition 1 for
        non-repeating container fields.
      parameters:
        - $ref: '#/components/parameters/Database'
        - $ref: '#/components/parameters/Layout'
        - $ref: '#/components/parameters/RecordId'
        - name: fieldName
          in: path
          required: true
          description: Name of the container field on the layout.
          schema:
            type: string
        - name: fieldRepetition
          in: path
          required: true
          description: One-based repetition number of the container field.
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - upload
              properties:
                upload:
                  type: string
                  contentMediaType: application/octet-stream
                  description: The file to store in the container field.
      responses:
        '200':
          description: File stored; the record's new modification id is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EditRecordResponse'
        '404':
          description: Record or field not found (FileMaker codes 101, 102).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        FileMaker file account credentials, used only on the login call.
        The account's privilege set must include the fmrest extended
        privilege.
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Session token from the login call. Expires after 15 minutes of
        inactivity; every successful call resets the timer.
  parameters:
    Database:
      name: database
      in: path
      required: true
      description: Hosted file name, without the .fmp12 extension.
      schema:
        type: string
    Layout:
      name: layout
      in: path
      required: true
      description: Layout providing the field context for the call.
      schema:
        type: string
    RecordId:
      name: recordId
      in: path
      required: true
      description: >-
        FileMaker internal record id, as returned by create, list, or find
        calls.
      schema:
        type: string
  schemas:
    Message:
      type: object
      description: Status message. Code is a string; success is "0".
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: FileMaker error code as a string. "0" means success.
          examples:
            - '0'
            - '401'
            - '952'
        message:
          type: string
          examples:
            - OK
    FieldValue:
      description: >-
        FileMaker field value. Text, number, date, time, and timestamp fields
        serialize as strings or numbers.
      type:
        - string
        - number
    FieldData:
      type: object
      description: Map of field name to value. Fields must exist on the layout in use.
      additionalProperties:
        $ref: '#/components/schemas/FieldValue'
    RecordData:
      type: object
      description: One record as returned by read, list, and find calls.
      required:
        - fieldData
        - recordId
        - modId
      properties:
        fieldData:
          $ref: '#/components/schemas/FieldData'
        portalData:
          type: object
          description: >-
            Related records from portals on the layout, keyed by portal object
            name or related table name.
          additionalProperties:
            type: array
            items:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/FieldValue'
        recordId:
          type: string
          description: Internal record id. Stable for the life of the record.
        modId:
          type: string
          description: >-
            Modification id. Increments on every change; used for optimistic
            locking.
    DataInfo:
      type: object
      description: Metadata about the found set. Drive paging from these values.
      properties:
        database:
          type: string
        layout:
          type: string
        table:
          type: string
        totalRecordCount:
          type: integer
          description: Records in the table.
        foundCount:
          type: integer
          description: Records matching the request.
        returnedCount:
          type: integer
          description: Records in this response.
    SessionResponse:
      type: object
      required:
        - response
        - messages
      properties:
        response:
          type: object
          properties:
            token:
              type: string
              description: Bearer token for subsequent calls.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
    RecordsResponse:
      type: object
      required:
        - response
        - messages
      properties:
        response:
          type: object
          properties:
            dataInfo:
              $ref: '#/components/schemas/DataInfo'
            data:
              type: array
              items:
                $ref: '#/components/schemas/RecordData'
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
    CreateRecordRequest:
      type: object
      required:
        - fieldData
      properties:
        fieldData:
          $ref: '#/components/schemas/FieldData'
        portalData:
          type: object
          description: Optional related records to create through portals on the layout.
          additionalProperties:
            type: array
            items:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/FieldValue'
    CreateRecordResponse:
      type: object
      required:
        - response
        - messages
      properties:
        response:
          type: object
          properties:
            recordId:
              type: string
              description: Id of the new record.
            modId:
              type: string
              description: Initial modification id, normally "0".
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
    EditRecordRequest:
      type: object
      required:
        - fieldData
      properties:
        fieldData:
          $ref: '#/components/schemas/FieldData'
        modId:
          type: string
          description: >-
            Optional. When present, the edit fails with FileMaker code 306 if
            the record's current modId differs.
    EditRecordResponse:
      type: object
      required:
        - response
        - messages
      properties:
        response:
          type: object
          properties:
            modId:
              type: string
              description: The record's new modification id.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
    SortSpec:
      type: object
      required:
        - fieldName
        - sortOrder
      properties:
        fieldName:
          type: string
        sortOrder:
          type: string
          enum:
            - ascend
            - descend
    FindRequest:
      type: object
      required:
        - query
      properties:
        query:
          type: array
          description: >-
            Query objects. Fields within one object are ANDed; objects are ORed
            against each other.
          items:
            type: object
            description: >-
              Field criteria, plus optional "omit" to exclude matches. All
              values are strings; criteria may carry find operators.
            additionalProperties:
              type: string
        sort:
          type: array
          items:
            $ref: '#/components/schemas/SortSpec'
        offset:
          type: string
          description: One-based offset, as a string (a quirk of this endpoint).
          default: '1'
        limit:
          type: string
          description: Maximum records to return, as a string (a quirk of this endpoint).
          default: '100'
    EmptyResponse:
      type: object
      required:
        - response
        - messages
      properties:
        response:
          type: object
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
    ErrorResponse:
      type: object
      description: >-
        Error envelope. Read messages[0].code for the FileMaker error; the HTTP
        status gives only the category.
      required:
        - messages
      properties:
        response:
          type: object
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
      example:
        response: {}
        messages:
          - code: '952'
            message: Invalid FileMaker Data API token (*)
