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

# Attach a data source to a data feed

> Attaches a new data source to a data feed. Each data feed can have at most one source per `source_type`. Returns the updated data feed including all attached sources.

Settings are validated on save; see `config` in the request body for the keys each `source_type` takes. Validation does not connect to the database or endpoint, so a wrong host, query or secret surfaces on the first run. `api` sources require HTTP API sources to be enabled for your account; otherwise returns 422 "Source type HTTP API is not enabled for this account". Contact Mangrove to enable them. Requires the `write:data_feed` token scope.




## OpenAPI

````yaml POST /data_feeds/{data_feed_friendly_id}/sources
openapi: 3.1.0
info:
  title: Mangrove - API
  version: 1.1.0
  description: >
    ## Filtering and sorting


    List endpoints that accept `filter` take one query parameter per condition,
    written `filter[field][comparator]=value`, for example
    `filter[name][contains]=North`. The comparators are `eq`, `neq`, `gt`,
    `geq`, `lt`, `leq` and `contains`, and `filter[field]=value` is short for
    `eq`. Conditions combine with AND. `contains` matches part of the value, is
    case-sensitive, and applies only to text fields. An unknown field or
    comparator, a value that doesn't fit the field's type, or a list or object
    as the value returns 400 with a message naming the problem. Each list
    operation names its filterable fields; some take only `eq`, and say so.


    `sort` takes a comma-separated list of fields, for example
    `sort=-created_at,name`. Each field sorts ascending, or descending with a
    `-` prefix. Unknown sort fields are ignored. Sorting by `id` orders by the
    record's internal ID, not its friendly ID. Each list operation names its
    sortable fields.
servers:
  - url: https://app.gomangrove.com/api/v1
security:
  - sec0: []
paths:
  /data_feeds/{data_feed_friendly_id}/sources:
    post:
      tags:
        - Data Ingestion
      summary: Attach a data source to a data feed
      description: >
        Attaches a new data source to a data feed. Each data feed can have at
        most one source per `source_type`. Returns the updated data feed
        including all attached sources.


        Settings are validated on save; see `config` in the request body for the
        keys each `source_type` takes. Validation does not connect to the
        database or endpoint, so a wrong host, query or secret surfaces on the
        first run. `api` sources require HTTP API sources to be enabled for your
        account; otherwise returns 422 "Source type HTTP API is not enabled for
        this account". Contact Mangrove to enable them. Requires the
        `write:data_feed` token scope.
      operationId: attach-data-feed-source
      parameters:
        - $ref: '#/components/parameters/DataFeedFriendlyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - source
              properties:
                source:
                  allOf:
                    - $ref: '#/components/schemas/DataFeedSourceInput'
                  required:
                    - name
                    - source_type
            examples:
              endpoint:
                summary: Accept file uploads through the API
                value:
                  source:
                    name: API uploads
                    source_type: endpoint
              sqlCursor:
                summary: SQL Server, continuing after the last value received
                value:
                  source:
                    name: Warehouse readings
                    source_type: sql
                    config:
                      adapter_type: sql_server
                      host: sql.example.com
                      port: '1433'
                      username: reader
                      database: operations
                      data_query: >-
                        SELECT * FROM readings WHERE recorded_at >
                        '{{cursor_value}}' ORDER BY recorded_at
                      fetch_mode: incremental
                      cursor_field: recorded_at
                      cursor_default: '2026-01-01T00:00:00Z'
                    credentials:
                      password: <secret>
              httpWindow:
                summary: HTTP API, one time window per run
                value:
                  source:
                    name: Meter API
                    source_type: api
                    config:
                      base_url: https://api.example.com
                      method: GET
                      path: v1/readings
                      headers:
                        Authorization: Bearer {{api_key}}
                      query:
                        from: '{{window_start}}'
                        to: '{{window_finish}}'
                      auth:
                        - name: api_key
                          type: static
                      response_format: json
                      rows_path: data.records
                      fetch_mode: incremental
                      cursor_field_type: timestamp
                      cursor_default: '2026-01-01T00:00:00Z'
                    credentials:
                      api_key: <secret>
      responses:
        '201':
          description: Source successfully attached. Returns the updated data feed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataFeed'
        '400':
          $ref: '#/components/responses/MissingParameter'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Data feed not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - message: Data feed not found
        '422':
          description: >-
            Validation failed. Each error names its `field`. An error that
            carries `config_key` names the `config` setting to fix, and its
            `code` is a stable identifier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                fetchSettings:
                  summary: Incremental window source missing its cursor settings
                  value:
                    errors:
                      - message: Config cursor_field_type must be declared.
                        field: config
                        code: cursor_type_missing
                        config_key: cursor_field_type
                      - message: Config cursor_default must be present.
                        field: config
                        code: cursor_default_missing
                        config_key: cursor_default
                unknownAdapter:
                  summary: Unsupported SQL adapter
                  value:
                    errors:
                      - message: >-
                          Config adapter_type must be one of sql_server,
                          snowflake_secure_share, snowflake_direct
                        field: config
                        code: unknown_adapter_type
                        config_key: adapter_type
                httpConfig:
                  summary: HTTP request settings rejected
                  value:
                    errors:
                      - message: Config credential `api_key` has no stored secret
                        field: config
                      - message: >-
                          Config path `/v1/readings` must not start with a
                          leading slash
                        field: config
                duplicateType:
                  summary: The data feed already has a source of this type
                  value:
                    errors:
                      - message: Source type has already been taken
                        field: source_type
                        code: taken
                        value: endpoint
components:
  parameters:
    DataFeedFriendlyId:
      name: data_feed_friendly_id
      in: path
      required: true
      description: The data feed's friendly ID (e.g. `datafd_aBcD1234eFgH5678`).
      schema:
        type: string
  schemas:
    DataFeedSourceInput:
      type: object
      properties:
        name:
          type: string
          description: Unique within the data feed.
          example: Warehouse readings
        source_type:
          type: string
          enum:
            - endpoint
            - ui_upload
            - sql
            - api
          description: >-
            `endpoint` accepts files uploaded through this API; `ui_upload`
            accepts files uploaded in the app; `sql` and `api` fetch data. A
            data feed holds at most one source of each type. `api` requires HTTP
            API sources to be enabled for your account.
          example: sql
        description:
          type:
            - string
            - 'null'
        config:
          $ref: '#/components/schemas/DataFeedSourceConfig'
        credentials:
          type: object
          writeOnly: true
          additionalProperties:
            type: string
          description: >-
            Write-only secrets, stored encrypted and never returned. SQL:
            `password` (`sql_server`) or `private_key` (`snowflake_direct`;
            `snowflake_secure_share` needs none). HTTP API: one value per
            `config.auth` entry, keyed by its `name`; every entry needs a
            secret, and keys with no matching entry are dropped.
          example:
            api_key: <secret>
        state:
          type: object
          additionalProperties: true
          description: >-
            Fetch progress, as returned in the source's `state`. Normally
            managed by Mangrove; set `cursor_value` to move the cursor.
    DataFeed:
      type: object
      description: >-
        A data feed. List rows omit each transformation's `script_content` and
        each source's `config`; single-feed responses include them.
      properties:
        id:
          type: string
          description: The data feed's friendly ID.
          example: datafd_aBcD1234eFgH5678
        name:
          type: string
          example: Daily Production Data
        description:
          type:
            - string
            - 'null'
          example: Imports daily production metrics
        headers_row_number:
          type: integer
          description: >-
            Row number (1-based) containing column headers. Rows above are
            ignored during ingestion. 1 until a schema sets another value.
          example: 1
        setup_error:
          type:
            - string
            - 'null'
          description: >-
            Why automatic setup could not build the schema (for example an
            unreadable sample file), or null.
        not_ready_message:
          type:
            - string
            - 'null'
          description: >-
            Why the feed cannot accept data yet, or null when it is ready. A new
            feed reports "Transformation scripts are still being generated"
            until its scripts are drafted, and "Schema not defined" until a
            schema exists.
          example: null
        schema_fields:
          type: array
          description: The columns of the current schema. Empty until a schema is defined.
          items:
            type: object
            properties:
              name:
                type: string
                example: site_name
              value_type:
                type: string
                enum:
                  - string
                  - number
                  - boolean
        event_types:
          type: array
          description: The event types the feed's active transformations create.
          items:
            type: object
            properties:
              id:
                type: integer
                description: >-
                  Numeric event type ID. Use `transformations[].event_type_id`
                  for the friendly ID.
              name:
                type: string
              slug:
                type: string
        transformations:
          type: array
          description: Active (unarchived) transformations, one per event type.
          items:
            type: object
            properties:
              id:
                type: string
                description: The transformation's friendly ID.
                example: tfn_aBcD1234eFgH5678
              event_type_id:
                type: string
                description: The event type's friendly ID.
                example: et_aBcD1234eFgH5678
              event_type_name:
                type: string
              script_status:
                type:
                  - string
                  - 'null'
                enum:
                  - generating
                  - ready
                  - failed
                  - null
                description: >-
                  Status of the automatically drafted script. null for a
                  transformation created without one.
              script_content:
                type:
                  - string
                  - 'null'
                description: >-
                  The latest script version, or null when there is none.
                  Single-feed responses only.
        sources:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                description: The source's friendly ID.
                example: dsrc_aBcD1234eFgH5678
              name:
                type: string
              source_type:
                type: string
                enum:
                  - endpoint
                  - ui_upload
                  - sql
                  - api
              config:
                $ref: '#/components/schemas/DataFeedSourceConfigResponse'
        ui_url:
          type: string
          format: uri
          description: Link to the data feed's page in the app.
          example: >-
            https://app.gomangrove.com/nexus/prj_aBcD1234eFgH5678/data-inputs/data-feeds/datafd_aBcD1234eFgH5678
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required:
        - errors
      properties:
        errors:
          type: array
          items:
            type: object
            required:
              - message
            properties:
              message:
                type: string
                description: Human-readable error message
              field:
                type: string
                description: Attribute the error applies to (validation errors only)
              code:
                type: string
                description: Machine-readable error code (validation errors only)
              blocking:
                type: array
                description: >-
                  Bounded preview of the records blocking a destroy
                  (blocked-destroy errors only)
                items:
                  $ref: '#/components/schemas/DeletionBlockingRecord'
              blocking_count:
                type: integer
                description: >-
                  Total count of records blocking a destroy (blocked-destroy
                  errors only)
              config_key:
                type: string
                description: >-
                  The data feed source config setting to fix, when the error is
                  on `config` (data feed source validation errors only)
              count:
                type: number
                description: >-
                  The limit or comparison value the failed validation checked
                  against, e.g. a maximum length or a numeric bound (validation
                  errors only, when the validation has one)
              value:
                description: >-
                  The rejected value, e.g. on an inclusion or uniqueness failure
                  (validation errors only, when the validation reports one)
    DataFeedSourceConfig:
      type: object
      description: >-
        Source settings. `endpoint` and `ui_upload` sources need none. Fetching
        sources (`sql`, `api`) take the keys below; any other key is stored as
        sent.


        **What each run fetches.** `fetch_mode` is `full` (everything, every
        run) or `incremental`. The request's tokens must agree with it: an
        incremental source uses either `{{cursor_value}}` (continue after the
        last value received) or both `{{window_start}}` and `{{window_finish}}`
        (the next time window), never both kinds; a full source uses none of
        them. When `fetch_mode` is omitted on create it is set from the tokens:
        `incremental` if any is present, otherwise `full`. A source whose tokens
        contradict its mode is rejected on save.


        A `{{cursor_value}}` source needs `cursor_field` and `cursor_default`;
        an HTTP API source also needs `cursor_field_type`. A SQL query may also
        use `{{cursor_field}}`, which is replaced by the `cursor_field` value,
        so a query that uses it needs `cursor_field` in every mode. A window
        source needs `cursor_default` and a `cursor_field_type` of `timestamp`,
        `epoch_s` or `epoch_ms`. A window `[window_start, window_finish)` ends
        `window_lag_seconds` before the fetch; with `max_window_seconds` set,
        each run fetches at most that span.


        **Credentials** never go in `config`; a `password` or `private_key` key
        here is rejected. Send secrets in the source's `credentials`.
      additionalProperties: true
      properties:
        fetch_mode:
          type: string
          enum:
            - full
            - incremental
        daily_fetch:
          type: boolean
          description: >-
            Fetch automatically once a day. Only one source per data feed can
            have it on. Defaults to `true` for a new incremental SQL source when
            no other source on the feed has it, otherwise `false`. A fetching
            source can only be run by this schedule or from the app; the API has
            no endpoint that starts a fetch.
        cursor_field:
          type: string
          description: >-
            The column in the returned rows that holds the cursor. After each
            run the cursor becomes this column's value in the last row.
        cursor_field_type:
          type: string
          description: >-
            `timestamp` (ISO 8601 with `Z` or an offset), `epoch_s` or
            `epoch_ms`. A window needs one of these. A `{{cursor_value}}` source
            accepts any value; `string` and `integer` are the conventional ones.
          example: timestamp
        cursor_default:
          type: string
          description: >-
            The cursor the first run starts from. A window source's value must
            parse as its `cursor_field_type`.
          example: '2026-01-01T00:00:00Z'
        window_lag_seconds:
          type: integer
          minimum: 0
          description: >-
            How far behind the fetch time each window ends, so late-arriving
            rows settle first. Default 300.
        max_window_seconds:
          type: integer
          minimum: 1
          description: >-
            The longest span one run fetches. When a run is capped and the
            source has `daily_fetch` on, a catch-up fetch starts once the
            source's newest run has published.
        window_start_override:
          type: string
          description: >-
            Write-only. Sets where the next window starts (written to
            `state.cursor_value`) and is not stored in `config`. Required when
            an existing source switches to window fetching or changes
            `cursor_field_type`; the start is inclusive.
        adapter_type:
          type: string
          description: >-
            SQL: `sql_server`, `snowflake_direct` or `snowflake_secure_share`
            (required). HTTP API: omit (or `rest`) for a single request, or
            `databricks_statement` for the Databricks SQL Statement Execution
            API.
        host:
          type: string
          description: >-
            `sql_server`: server host. With `port`, where the `password`
            credential is sent.
        port:
          type: string
          description: '`sql_server`: server port, e.g. `1433`.'
        username:
          type: string
          description: '`sql_server` and `snowflake_direct`.'
        database:
          type: string
          description: All SQL adapters.
        schema:
          type: string
          description: '`snowflake_direct` and `snowflake_secure_share`.'
        account_url:
          type: string
          description: >-
            `snowflake_direct`: the account URL the `private_key` credential
            signs for.
        org_name:
          type: string
          description: '`snowflake_direct`.'
        account_name:
          type: string
          description: '`snowflake_direct`.'
        warehouse:
          type: string
          description: '`snowflake_direct`.'
        pre_query:
          type: string
          description: >-
            SQL only. Runs before the data query; its first row's columns become
            `{{column}}` tokens in `data_query`. A change in its result restarts
            a `{{cursor_value}}` source from `cursor_default`. Cannot be
            combined with a window.
        data_query:
          type: string
          description: >-
            SQL only. The query each run executes. Not checked on save beyond
            its tokens; a missing or failing query surfaces on the run.
          example: >-
            SELECT * FROM readings WHERE recorded_at > '{{cursor_value}}' ORDER
            BY recorded_at
        base_url:
          type: string
          description: >-
            HTTP API only, required. Absolute URL; must be `https` when the
            source declares credentials.
          example: https://api.example.com
        method:
          type:
            - string
            - 'null'
          enum:
            - GET
            - POST
            - null
          description: >-
            HTTP API only. Default `GET`; `databricks_statement` requires
            `POST`.
        path:
          type: string
          description: >-
            HTTP API only, required. Relative to `base_url`, with no leading
            slash and no scheme. May contain tokens.
          example: v1/readings
        headers:
          type:
            - object
            - 'null'
          additionalProperties:
            type: string
          description: >-
            HTTP API only. String values, no line breaks. May contain tokens,
            e.g. `Authorization` set to `Bearer {{api_key}}`. Values are stored
            and returned as entered, so put a secret in `credentials` and
            reference it as `{{name}}`.
        query:
          type:
            - object
            - 'null'
          additionalProperties:
            type: string
          description: >-
            HTTP API only. Query parameters; string values that may contain
            tokens. Values are stored and returned as entered, so put a secret
            in `credentials` and reference it as `{{name}}`.
        body:
          type:
            - string
            - 'null'
          description: >-
            HTTP API only. A JSON document sent as a string; not allowed with
            `GET`. For `databricks_statement` it is required and must hold
            `warehouse_id` and `statement` (optional `catalog`, `schema`,
            `format` `JSON_ARRAY`, `disposition` `INLINE` or `EXTERNAL_LINKS`);
            bind window bounds through its `parameters`, since `statement` may
            not contain `{{` tokens.
        auth:
          type:
            - array
            - 'null'
          description: >-
            HTTP API only. The credentials the request references. Each entry's
            secret is sent in `credentials` under the entry's `name`, and
            referenced in the request as `{{name}}`.
          items:
            $ref: '#/components/schemas/DataFeedSourceAuthEntry'
        response_format:
          type:
            - string
            - 'null'
          enum:
            - csv
            - json
            - null
          description: HTTP API (single request) only. Default `csv`.
        rows_path:
          type:
            - string
            - 'null'
          description: >-
            HTTP API (single request), JSON only. Dotted path to the list of row
            objects, e.g. `data.records`; omit when the whole response is the
            list.
        success_when:
          type:
            - object
            - 'null'
          description: >-
            HTTP API (single request) only. Treats a 2xx response as failed
            unless the value at `path` is one of `equals`; values in `pending`
            report the result as not ready yet.
          properties:
            path:
              type: string
              example: status.state
            equals:
              type: array
              items: {}
            pending:
              type: array
              items: {}
        max_response_bytes:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 52428800
          description: HTTP API only. Largest response accepted. Default and maximum 50 MB.
        deadline_seconds:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 1800
          description: HTTP API only. Time limit for a fetch. Default 300.
        poll:
          type:
            - object
            - 'null'
          description: >-
            `databricks_statement` only. Polling budget: `deadline_seconds`
            (default 240), `interval_seconds` (default 2) and optional
            `max_attempts`, each a positive integer.
          additionalProperties: false
          properties:
            deadline_seconds:
              type: integer
            interval_seconds:
              type: integer
            max_attempts:
              type: integer
        link_expiry_retries:
          type: integer
          minimum: 0
          description: >-
            `databricks_statement` only. How often to re-request expired result
            links. Default 2.
    DataFeedSourceConfigResponse:
      type: object
      description: >-
        The source's stored `config`, with the keys described for the request
        body. Scalar values are returned as strings, so `daily_fetch` reads
        `"true"` and a port `"1433"`; objects and arrays (`headers`, `query`,
        `auth`, `poll`, `success_when`) are returned as JSON. Credentials never
        appear here, but values typed directly into `headers` or `query` are
        stored and returned as entered.
      additionalProperties:
        oneOf:
          - type: string
          - type: object
            additionalProperties: true
          - type: array
            items: {}
    DeletionBlockingRecord:
      type: object
      required:
        - type
      properties:
        type:
          type: string
        friendly_id:
          type: string
        name:
          type:
            - string
            - 'null'
          description: >-
            Display label, and the only identifier for records that have no
            friendly_id
    DataFeedSourceAuthEntry:
      type: object
      required:
        - name
        - type
      properties:
        name:
          type: string
          pattern: ^[a-z][a-z0-9_]*$
          description: >-
            Unique within the source. `cursor_value`, `window_start` and
            `window_finish` are reserved.
          example: api_key
        type:
          type: string
          enum:
            - static
            - oauth2_cc
          description: >-
            `static` inserts the secret as it is; `oauth2_cc` exchanges it as an
            OAuth2 client secret for an access token, which is inserted instead.
        token_url:
          type: string
          description: '`oauth2_cc` only, required. Must be `https`.'
        client_id:
          type: string
          description: '`oauth2_cc` only, required.'
        scope:
          type: string
          description: '`oauth2_cc` only, optional.'
  responses:
    MissingParameter:
      description: >-
        A required parameter or body object is missing or empty. The message
        names it.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - message: 'param is missing or the value is empty or invalid: project'
    Unauthorized:
      description: >-
        The API token is missing, invalid or expired. The body is empty, except
        for an expired token, which returns the error below.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - message: API token has expired
    Forbidden:
      description: >-
        The token lacks the scope the operation's description names, or a
        condition the description states applies, such as a feature that isn't
        enabled for your account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - message: You are not authorized to perform this action
  securitySchemes:
    sec0:
      type: apiKey
      in: header
      name: Authorization
      x-bearer-format: ''

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.