openapi: 3.0.0
info:
  title: Softr Database API
  description: |
    Public API to communicate with Softr Databases. The API follows REST semantics and uses JSON to encode objects.
    
    **Rate Limiting:**
    All calls are subject to rate limiting. On exceeding the limit, you will receive a 429 HTTP Response Status Code.
    - **Reads** (GET, POST /search): 40 requests/second per token.
    - **Writes** (POST, PUT, PATCH, DELETE): 30 requests/second per token.
  version: "1.0.0"
servers:
  - url: https://tables-api.softr.io/api/v1
    description: Production Server

tags:
  - name: Databases
    description: Operations related to Softr databases.
  - name: Tables
    description: Operations related to tables within a database.
  - name: Table Fields
    description: Operations to manage fields within a table.
  - name: Records
    description: Operations to manage records (rows) within a table.

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Softr-Api-Key
  
  schemas:
    # GENERAL
    Error:
      type: object
      properties:
        message:
          type: string
          description: A human-readable error message.
        errorCode:
          type: string
          description: A machine-readable error code (e.g. BAD_REQUEST, VALIDATION_ERROR, RESOURCE_NOT_FOUND).
        details:
          type: object
          description: Additional context about the error (e.g. fieldId, traceId).
          additionalProperties: true

    # DATABASES
    Database:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        workspaceId:
          type: string
        tablesCount:
          type: integer
          description: Number of tables in this database.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    # TABLES
    TableField:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
          description: "Optional description of the field."
        type:
          type: string
          description: "The type of the field."
          enum: [SINGLE_LINE_TEXT, CHECKBOX, CURRENCY, DATE, DATETIME, DURATION, EMAIL, SELECT, NUMBER, ATTACHMENT, RATING, LINKED_RECORD, LONG_TEXT, URL, PERCENT, PHONE, LOOKUP, ROLLUP, FORMULA, CREATED_AT, UPDATED_AT, CREATED_BY, UPDATED_BY, AUTONUMBER, RECORD_ID, USER]
        options:
          type: object
          description: "Configuration options for the field, specific to its type. See the official documentation for the structure of each type."
          additionalProperties: true
        allowMultipleEntries:
          type: boolean
        readonly:
          type: boolean
        required:
          type: boolean
        locked:
          type: boolean
        defaultValue:
          type: string
          nullable: true
        aiOptions:
          type: object
          nullable: true
          description: "AI-powered auto-fill configuration. Null if not configured."
          properties:
            aiFillable:
              type: boolean
            aiOnly:
              type: boolean
            allowWebSearch:
              type: boolean
            aiModel:
              type: string
            prompt:
              type: string
            canBeTriggeredManually:
              type: boolean
            runWhenRecordIsCreated:
              type: boolean
            runWhenRecordIsUpdated:
              type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    
    Table:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        primaryFieldId:
          type: string
        defaultViewId:
          type: string
        fields:
          type: array
          items:
            $ref: '#/components/schemas/TableField'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    TableView:
      type: object
      properties:
        id:
          type: string
        tableId:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
          
    # RECORDS
    Record:
      type: object
      properties:
        id:
          type: string
        tableId:
          type: string
        fields:
          type: object
          description: "A map of Field IDs to their values for this record."
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
          
    RecordListMetadata:
      type: object
      properties:
        offset:
          type: integer
        limit:
          type: integer
        total:
          type: integer
          
    # FILTERING
    FilterCondition:
      type: object
      description: "A condition for filtering records. Text-related conditions are case-insensitive."
      properties:
        operator:
          type: string
          enum: [AND, OR, IS_EMPTY, IS_NOT_EMPTY, IS_BETWEEN, IS_NOT_BETWEEN, IS_WITHIN, IS_NOT_WITHIN, IS, IS_NOT, GREATER_THAN, GREATER_THAN_OR_EQUALS, LESS_THAN, LESS_THAN_OR_EQUALS, CONTAINS, DOES_NOT_CONTAIN, STARTS_WITH, DOES_NOT_START_WITH, ENDS_WITH, DOES_NOT_END_WITH, IS_ONE_OF, IS_NOT_ONE_OF, HAS_ANY_OF, HAS_ALL_OF, HAS_NONE_OF]
        leftSide:
          type: string
          description: "The Field ID to apply the operator on."
        rightSide:
          description: "The value to compare against. Can be a string, number, or an array for operators like IS_ONE_OF."
          oneOf:
            - type: string
            - type: number
            - type: boolean
            - type: array
              items:
                type: string
        lowerBound:
          type: string
          description: "Lower bound for IS_BETWEEN operator."
        upperBound:
          type: string
          description: "Upper bound for IS_BETWEEN operator."
        conditions:
          type: array
          description: "Used for AND/OR operators to nest conditions."
          items:
            $ref: '#/components/schemas/FilterCondition'

security:
  - ApiKeyAuth: []

paths:
  /databases:
    get:
      tags:
        - Databases
      summary: Get Databases
      description: Retrieve a list of all databases accessible to the authenticated user.
      operationId: getDatabases
      responses:
        '200':
          description: A list of databases.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Database'
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      tags:
        - Databases
      summary: Create a Database
      description: Create a new database in the specified workspace.
      operationId: createDatabase
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - workspaceId
                - name
              properties:
                workspaceId:
                  type: string
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: The newly created database.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Database'
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Databases
      summary: Get Single Database
      description: Retrieve details of a specific database by its ID.
      operationId: getDatabaseById
      responses:
        '200':
          description: Database details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Database'
        '429':
          description: Rate limit exceeded.
    put:
      tags:
        - Databases
      summary: Update Database
      description: Update the details of an existing database.
      operationId: updateDatabase
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: The updated database.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Database'
        '429':
          description: Rate limit exceeded.
    delete:
      tags:
        - Databases
      summary: Delete Database
      description: Delete a specific database by its ID. It can only be deleted if it is empty, unless the 'force' parameter is used.
      operationId: deleteDatabase
      parameters:
        - name: force
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '204':
          description: Database deleted successfully.
        '400':
          description: Bad Request - Database is not empty and force=true was not provided.
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Tables
      summary: Get Tables
      description: Retrieve a list of all tables in the specified database.
      operationId: getTables
      responses:
        '200':
          description: A list of tables.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Table'
        '429':
          description: Rate limit exceeded.
    post:
      tags:
        - Tables
      summary: Create Table
      description: Create a new table in the specified database.
      operationId: createTable
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                primaryFieldName:
                  type: string
                fields:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      type:
                        type: string
                      options:
                        type: object
                        additionalProperties: true
      responses:
        '200':
          description: The newly created table.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Table'
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables/{tableId}:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Tables
      summary: Get Single Table
      description: Retrieve details of a specific table by its ID.
      operationId: getTableById
      responses:
        '200':
          description: Table details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Table'
        '429':
          description: Rate limit exceeded.
    put:
      tags:
        - Tables
      summary: Update Table
      description: Update a table's name and/or description. To modify fields, use the field-specific endpoints.
      operationId: updateTable
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: The updated table.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Table'
        '429':
          description: Rate limit exceeded.
    delete:
      tags:
        - Tables
      summary: Delete Table
      description: Delete a specific table by its ID. It can only be deleted if it is empty, unless the 'force' parameter is used.
      operationId: deleteTable
      parameters:
        - name: force
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '204':
          description: Table deleted successfully.
        '400':
          description: Bad Request - Table is not empty and force=true was not provided.
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables/{tableId}/views:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Tables
      summary: Get Table Views
      description: Retrieve a list of views for a specific table.
      operationId: getTableViews
      responses:
        '200':
          description: A list of table views.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/TableView'
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables/{tableId}/fields:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Table Fields
      summary: Add Table Field
      description: Add a new field to a table.
      operationId: addTableField
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - type
              properties:
                name:
                  type: string
                type:
                  type: string
                options:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: The newly created field.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/TableField'
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables/{tableId}/fields/{fieldId}:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
      - name: fieldId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Table Fields
      summary: Get Table Field
      description: Retrieve details of a specific field.
      operationId: getTableField
      responses:
        '200':
          description: Field details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/TableField'
        '429':
          description: Rate limit exceeded.
    put:
      tags:
        - Table Fields
      summary: Update Table Field
      description: Update the properties of a specific field.
      operationId: updateTableField
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                type:
                  type: string
                options:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: The updated field.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/TableField'
        '429':
          description: Rate limit exceeded.
    delete:
      tags:
        - Table Fields
      summary: Delete Table Field
      description: Delete a specific field from a table.
      operationId: deleteTableField
      responses:
        '204':
          description: Field deleted successfully.
        '429':
          description: Rate limit exceeded.

  /databases/{databaseId}/tables/{tableId}/records:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Records
      summary: Get Records
      description: Retrieve a list of all records in the specified table.
      operationId: getRecords
      parameters:
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
        - name: fieldNames
          in: query
          description: "If true, use field names as keys in the fields object instead of field IDs."
          schema:
            type: boolean
            default: false
        - name: viewId
          in: query
          description: "Filter records by a specific view."
          schema:
            type: string
      responses:
        '200':
          description: A paginated list of records.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Record'
                  metadata:
                    $ref: '#/components/schemas/RecordListMetadata'
        '429':
          description: Rate limit exceeded.
    post:
      tags:
        - Records
      summary: Create Record
      description: Create a new record in the specified table.
      operationId: createRecord
      parameters:
        - name: fieldNames
          in: query
          description: "If true, use field names as keys in the response fields object instead of field IDs."
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  description: "A map of Field IDs to their values."
                  additionalProperties: true
      responses:
        '200':
          description: The newly created record.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Record'
        '429':
          description: Rate limit exceeded.
          
  /databases/{databaseId}/tables/{tableId}/records/search:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
    post:
      tags:
        - Records
      summary: Search Records
      description: Search for records in the specified table based on filter, sort, and pagination criteria.
      operationId: searchRecords
      parameters:
        - name: fieldNames
          in: query
          description: "If true, use field names as keys in the fields object instead of field IDs."
          schema:
            type: boolean
            default: false
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                filter:
                  type: object
                  properties:
                    condition:
                      $ref: '#/components/schemas/FilterCondition'
                sorting:
                  type: array
                  items:
                    type: object
                    properties:
                      sortingField:
                        type: string
                        description: "The Field ID to apply the sorting on"
                      sortType:
                        type: string
                        enum: [ASC, DESC]
                paging:
                  type: object
                  properties:
                    offset:
                      type: integer
                      default: 0
                    limit:
                      type: integer
                      default: 10
      responses:
        '200':
          description: A paginated list of matching records.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Record'
                  metadata:
                    $ref: '#/components/schemas/RecordListMetadata'
        '429':
          description: Rate limit exceeded.
          
  /databases/{databaseId}/tables/{tableId}/records/{recordId}:
    parameters:
      - name: databaseId
        in: path
        required: true
        schema:
          type: string
      - name: tableId
        in: path
        required: true
        schema:
          type: string
      - name: recordId
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Records
      summary: Get Single Record
      description: Retrieve details of a specific record by its ID.
      operationId: getRecordById
      parameters:
        - name: fieldNames
          in: query
          description: "If true, use field names as keys in the fields object instead of field IDs."
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Record details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Record'
        '429':
          description: Rate limit exceeded.
    patch:
      tags:
        - Records
      summary: Update Record
      description: Partially update a specific record by its ID. Only the fields provided in the request body will be changed.
      operationId: updateRecord
      parameters:
        - name: fieldNames
          in: query
          description: "If true, use field names as keys in the response fields object instead of field IDs."
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  description: "A map of Field IDs to their new values."
                  additionalProperties: true
      responses:
        '200':
          description: The updated record.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Record'
        '429':
          description: Rate limit exceeded.
    delete:
      tags:
        - Records
      summary: Delete Record
      description: Delete a specific record by its ID.
      operationId: deleteRecord
      responses:
        '204':
          description: Record deleted successfully.
        '429':
          description: Rate limit exceeded.