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

# Declares a lead source: where leads Erdo did not capture itself come from,


> and which lead dataset they are written into, each exactly once.

  - provider "linkedin\_ads": an ad account's LinkedIn Lead Gen Form responses, read every fifteen minutes. Every form must belong to the account.
  - provider "google\_sheets": a sheet tab open to anyone with the link (sheet\_url), read every five minutes. Rows submitted before the source was declared are written as history; later rows as new leads.
  - provider "webhook": a tool posts each lead to webhook\_url with the X-Erdo-Lead-Source-Secret header. The secret is returned once.
  - provider "import": files sent to POST /v1/provider-lead-sources/import.

Sheet, webhook and import sources are named and carry a column mapping (mapping.lead\_source, format, row\_id, submitted\_at, columns); only mapped fields are read. dry\_run previews what a source would write without saving. History is written with the lead's own arrival and is never alerted on, billed or followed up until somebody starts it. The dataset must have lead identity turned on. Saving replaces the declaration with the same name (or provider and account).

REST only, with no MCP tool, like scheduled actions: connecting an ad account's leads to a dataset is a commitment about whose prospects land where, made by somebody who holds the connection, not a step an agent takes mid-task.




## OpenAPI

````yaml /api/openapi.json put /v1/provider-lead-sources
openapi: 3.0.0
info:
  title: Erdo API
  description: >-
    Erdo's REST API: query and write datasets, run agents, manage threads,
    integrations, pages, evals, workstreams, experiments, and bounded outreach.
    Authenticate with a Bearer API key (erdo_api_...) or scoped token
    (erdo_token_...).
  version: '2026-11-02'
servers:
  - url: https://api.erdo.ai
    description: Production
security:
  - bearerAuth: []
paths:
  /v1/provider-lead-sources:
    put:
      summary: >
        Declares a lead source: where leads Erdo did not capture itself come
        from,
      description: >
        and which lead dataset they are written into, each exactly once.

          - provider "linkedin\_ads": an ad account's LinkedIn Lead Gen Form responses, read every fifteen minutes. Every form must belong to the account.
          - provider "google\_sheets": a sheet tab open to anyone with the link (sheet\_url), read every five minutes. Rows submitted before the source was declared are written as history; later rows as new leads.
          - provider "webhook": a tool posts each lead to webhook\_url with the X-Erdo-Lead-Source-Secret header. The secret is returned once.
          - provider "import": files sent to POST /v1/provider-lead-sources/import.

        Sheet, webhook and import sources are named and carry a column mapping
        (mapping.lead\_source, format, row\_id, submitted\_at, columns); only
        mapped fields are read. dry\_run previews what a source would write
        without saving. History is written with the lead's own arrival and is
        never alerted on, billed or followed up until somebody starts it. The
        dataset must have lead identity turned on. Saving replaces the
        declaration with the same name (or provider and account).


        REST only, with no MCP tool, like scheduled actions: connecting an ad
        account's leads to a dataset is a commitment about whose prospects land
        where, made by somebody who holds the connection, not a step an agent
        takes mid-task.
      operationId: PUT:mcp.SetProviderLeadSourceAPI
      requestBody:
        content:
          application/json:
            schema:
              properties:
                account_id:
                  title: |
                    account_id is the provider's numeric ad account id.
                  type: string
                connection_id:
                  description: >
                    (integration id) for the provider; Pipedream connector
                    account ids are refused.
                  title: >
                    connection_id is the id of this organization's connected
                    integration
                  type: string
                create_only:
                  description: >
                    import source with this name exists, so adding a source
                    never replaces one.
                  title: >
                    create_only refuses the save (already_exists) when a sheet,
                    webhook or
                  type: boolean
                dataset:
                  title: >
                    dataset is the slug of a lead dataset with lead identity
                    turned on.
                  type: string
                dry_run:
                  description: >
                    write, saving nothing. For a sheet it reads the tab; for a
                    webhook it maps sample.
                  title: >
                    dry_run checks the declaration and returns a preview of what
                    it would
                  type: boolean
                enabled:
                  description: >
                    Saving an existing sheet, webhook or import source without
                    enabled keeps its current state, so rotating a paused
                    webhook's secret leaves it paused.
                  title: >
                    enabled defaults to true. False keeps the declaration but
                    stops reading it.
                  type: boolean
                forms:
                  description: |
                    account\_id.
                  items:
                    type: string
                  title: >
                    forms are the lead form ids to write responses from. Each
                    must belong to
                  type: array
                if_updated_at:
                  description: >
                    read. The save is refused (aborted) when the source was
                    saved since, so two people editing one source cannot
                    overwrite each other's change.
                  title: >
                    if_updated_at is a sheet, webhook or import source's
                    updated_at as last
                  type: string
                mapping:
                  $ref: '#/components/schemas/eventpipeline.LeadSourceMapping'
                name:
                  description: >
                    digits and hyphens, unique in the organization. Saving the
                    same name again replaces that source.
                  title: >
                    name names a google_sheets, webhook or import source:
                    lower-case letters,
                  type: string
                provider:
                  title: >
                    provider is the ad platform. "linkedin_ads" is the only one
                    supported.
                  type: string
                rotate_secret:
                  description: |
                    working at once.
                  title: >
                    rotate_secret issues a webhook source a new secret; the old
                    one stops
                  type: boolean
                sample:
                  title: |
                    sample is a webhook body to preview the mapping against.
                  type: object
                sheet_url:
                  description: >
                    (including #gid=). The tab must be open to anyone with the
                    link.
                  title: >
                    sheet_url is a google_sheets source's tab, as its address in
                    the browser
                  type: string
                suggest_columns:
                  description: >
                    wants the sheet's fields matched to (is\_broker, budget,
                    timeline, phone, name), each with what it records. The
                    preview suggests a field only for one of these, and leaves
                    every other field without a suggestion. Without it the lead
                    dataset's own columns are used.
                  items:
                    $ref: '#/components/schemas/eventpipeline.LeadSourceSuggestColumn'
                  title: >
                    suggest_columns, on a sheet's dry run, are the lead columns
                    the caller
                  type: array
              type: object
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  account_id:
                    description: |
                      owns are ever written.
                    title: >
                      account_id is the provider's ad account id. Only responses
                      this account
                    type: string
                  connection_id:
                    description: |
                      responses are read through.
                    title: >
                      connection_id is the organization's connected integration
                      account the
                    type: string
                  created_at:
                    title: >
                      created_at is when the declaration was first saved (RFC
                      3339).
                    type: string
                  dataset:
                    title: >
                      dataset is the slug of the lead dataset responses are
                      written into.
                    type: string
                  enabled:
                    title: |
                      enabled is whether the platform is reading this source.
                    type: boolean
                  forms:
                    items:
                      type: string
                    title: |
                      forms are the lead form ids whose responses are written.
                    type: array
                  id:
                    title: >
                      id is the declaration's id. A webhook source's address
                      carries it.
                    type: string
                  last_delivery_at:
                    description: |
                      including one that matched an existing lead (RFC 3339).
                    title: >
                      last_delivery_at is when the source last received a
                      non-history response,
                    type: string
                  last_error:
                    title: >
                      last_error is why the last read did not finish, or empty
                      when it did.
                    type: string
                  last_run_at:
                    description: |
                      empty when it never has.
                    title: >
                      last_run_at is when the platform last read this source
                      (RFC 3339), or
                    type: string
                  mapping:
                    $ref: '#/components/schemas/eventpipeline.LeadSourceMapping'
                  name:
                    description: >
                      ("brickell-meta-forms-spreadsheet"); empty for a LinkedIn
                      declaration.
                    title: >
                      name is how people refer to a sheet, webhook or import
                      source
                    type: string
                  preview:
                    $ref: '#/components/schemas/eventpipeline.LeadSourcePreview'
                  provider:
                    description: |
                      "webhook" or "import".
                    title: >
                      provider is the kind of source: "linkedin_ads",
                      "google_sheets",
                    type: string
                  read_via:
                    description: >
                      sheet's open link. It stops working if link sharing is
                      turned off.
                    title: >
                      read_via says how a sheet source reads its tab: "link",
                      through the
                    type: string
                  sheet_url:
                    title: |
                      sheet_url is a sheet source's tab.
                    type: string
                  synced_through:
                    description: >
                      handled through (RFC 3339), or empty before the first
                      complete sync.
                    title: >
                      synced_through is the submission time every earlier
                      response has been
                    type: string
                  updated_at:
                    description: >
                      microsecond). Send it back as if\_updated\_at to save only
                      an unchanged source.
                    title: >
                      updated_at is when the declaration was last saved (RFC
                      3339, to the
                    type: string
                  webhook_secret:
                    description: >
                      returned once, when the source is created or the secret
                      rotated, and is never stored.
                    title: >
                      webhook_secret is sent in the X-Erdo-Lead-Source-Secret
                      header. It is
                    type: string
                  webhook_url:
                    title: >
                      webhook_url is where a webhook source's tool posts each
                      lead.
                    type: string
                type: object
          description: Success response
        default:
          $ref: '#/components/responses/APIError'
components:
  schemas:
    eventpipeline.LeadSourceMapping:
      properties:
        columns:
          additionalProperties:
            type: string
          description: >
            (or by the format) are never read, unless keep\_other\_fields is
            set.
          title: >
            columns maps an incoming field to a lead column. Fields not named
            here
          type: object
        consent:
          allOf:
            - $ref: '#/components/schemas/eventpipeline.LeadSourceConsent'
          description: >
            box (method "checkbox") or a notice beside the submit button
            ("disclosure"), with the exact wording shown.


            When the mapping reads a consent column, the column is each person's
            answer and this says how it was asked: a yes gets this method and
            wording, a no stays no, and a blank stays blank (an optional box
            left unticked). When it reads none, everyone who submitted agreed
            this way, so every lead is recorded as having agreed; that is true
            of a box only when ticking it was required. Without it nothing is
            recorded where a row has no answer, and the lead is not texted. A
            webhook posts consent per lead instead.
          title: >
            consent is how the source's form asked about calls and texts: an
            opt-in
        default_country:
          description: |
            code belongs to ("US").
          title: |
            default_country is the ISO country a phone written without a country
          type: string
        format:
          description: >
            "meta\_lead\_export", which maps the exporter's standard fields
            itself.
          title: |
            format is "columns" (the mapping alone) or a preset such as
          type: string
        ignore:
          description: >
            not read: Meta's "company\_name", say, when the company is not
            wanted. Meta's "id" cannot be ignored, it identifies the lead; and
            email and phone cannot both be, a lead is somebody Erdo can reach.
          items:
            type: string
          title: >
            ignore lists fields the format reads by itself that this source
            should
          type: array
        keep_other_fields:
          description: >
            the lead, under the field's own name made into a column name ("Are
            you a broker?" becomes are\_you\_a\_broker). Only a webhook may set
            it: the tool posting to it sends exactly what its author chose,
            whereas a sheet carries the sales desk's own notes beside the leads.
            A field whose column the platform writes itself, or that a mapped
            field already fills, is skipped.
          title: >
            keep_other_fields writes every posted field the mapping does not
            name to
          type: boolean
        lead_source:
          description: >
            meta\_lead\_form, linkedin\_lead\_gen\_form or imported\_list. It is
            never free text, because the follow-up engine has a sentence for
            each value. It is optional when what\_people\_did says it instead,
            and the meta\_lead\_export format implies meta\_lead\_form.
          title: |
            lead_source is what the person did, from the platform's vocabulary:
          type: string
        row_id:
          description: |
            the source. A row is written once per id.
          items:
            type: string
          title: >
            row_id is the field, or the fields together, that identify one lead
            in
          type: array
        submitted_at:
          title: |
            submitted_at is the field that says when the person got in touch.
          type: string
        values:
          additionalProperties:
            additionalProperties:
              type: string
            type: object
          description: >
            by the answer as the source writes it: is\_broker {"Yes, I'm an
            agent": "true"}. A form asks its questions in its own words, and a
            list reading the column reads the dataset's values, not the form's.
            An answer the map does not name is kept as written and reported
            (unmappedValues), never guessed. The lookup ignores case and
            surrounding spaces.
          title: >
            values maps answers to the values a lead column holds, by column,
            then
          type: object
        what_people_did:
          description: >
            before this source sent them ("Filled in the contact form on our
            partner portal"). It is stamped on each lead the source creates
            (import\_source\_description), where the follow-up reads it as the
            development's account of how the person got in touch. Required for a
            webhook, which is for custom integrations, and for a sheet or a file
            without a lead\_source.
          title: |
            what_people_did is the development's own words for what someone did
          type: string
      title: |
        LeadSourceMapping is a declared source's column mapping.
      type: object
    eventpipeline.LeadSourceSuggestColumn:
      properties:
        description:
          description: |
            agent rather than a buyer: true or false").
          title: |
            description is what it records ("whether the person is a broker or
          type: string
        name:
          title: |
            name is the lead column ("is_broker").
          type: string
      title: >
        LeadSourceSuggestColumn is a lead column a sheet's fields may be matched
        to.
      type: object
    eventpipeline.LeadSourcePreview:
      properties:
        already_written:
          format: int64
          title: |
            already_written is how many the ledger has already dealt with.
          type: integer
        existing_columns:
          description: |
            a surface can say a suggestion matches one.
          items:
            type: string
          title: >
            existing_columns are the lead dataset's own columns a source may
            write, so
          type: array
        format_columns:
          additionalProperties:
            type: string
          description: >
            one lead column each is read as when it is read: Meta's
            company\_name as company, campaign\_name as ad\_campaign\_name. A
            surface offers such a field only that column or nothing.
          title: >
            format_columns are the fields the format itself knows, by field, and
            the
          type: object
        header:
          description: |
            body's keys.
          items:
            type: string
          title: >
            header is the fields the source offers: a sheet's header row, a
            sample
          type: array
        history:
          description: >
            before the source was declared. No alert, no follow-up until
            started.
          format: int64
          title: >
            history is how many would be written as history: leads who got in
            touch
          type: integer
        missing_fields:
          items:
            type: string
          title: |
            missing_fields are fields the mapping needs and the header lacks.
          type: array
        new:
          format: int64
          title: |
            new is how many would be written as leads getting in touch now.
          type: integer
        not_read:
          items:
            type: string
          title: >
            not_read are the fields the mapping leaves out, which are never
            written.
          type: array
        read_columns:
          additionalProperties:
            type: string
          description: >
            column each becomes: what was named in columns and what the format
            reads by itself (Meta's company\_name as company), so a person sees
            every field a lead gets from the source.
          title: >
            read_columns are the fields the mapping reads, by field, and the
            lead
          type: object
        rows:
          format: int64
          title: |
            rows is how many rows the source holds now.
          type: integer
        sample:
          items:
            type: object
          title: |
            sample is the first rows as they would be written.
          type: array
        skipped:
          additionalProperties:
            format: int64
            type: integer
          title: |
            skipped counts rows that would not be written, by reason.
          type: object
        suggested_columns:
          additionalProperties:
            type: string
          description: |
            person to confirm.
          title: >
            suggested_columns proposes a lead column for each field not read,
            for a
          type: object
        suggested_values:
          additionalProperties:
            additionalProperties:
              type: string
            type: object
          description: >
            answers, the value each answer is stored as in its lead column, by
            field then answer: "are\_you\_a\_broker?" {"Yes, I'm an agent":
            "true"}. Saved as the mapping's values once a person confirms them.
          title: |
            suggested_values proposes, for a field holding a small fixed set of
          type: object
        test:
          format: int64
          title: |
            test is how many the source marks as test submissions.
          type: integer
      title: |
        LeadSourcePreview is a dry run's account of what a source would write.
      type: object
    eventpipeline.LeadSourceConsent:
      properties:
        method:
          description: |
            or "disclosure" (they submitted next to a consent notice).
          title: >
            method is "checkbox" (they ticked a required opt-in box: express
            consent)
          type: string
        text:
          description: |
            the record of what they agreed to.
          title: >
            text is the consent wording the form showed, exactly, when it is
            known:
          type: string
      title: >
        LeadSourceConsent is the consent a source's form collected from
        everyone.
      type: object
  responses:
    APIError:
      content:
        application/json:
          schema:
            externalDocs:
              url: https://pkg.go.dev/encore.dev/beta/errs#Error
            properties:
              code:
                description: Error code
                example: not_found
                externalDocs:
                  url: https://pkg.go.dev/encore.dev/beta/errs#ErrCode
                type: string
              details:
                description: Error details
                type: object
              message:
                description: Error message
                type: string
            title: APIError
            type: object
      description: Error response
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).

````

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