{
  "openapi": "3.0.4",
  "info": {
    "title": "Funnelfeedr External API",
    "description": "Prospecting data from Funnelfeedr for your own tools and agents: match organizations and people in the Nordic registry data, read organizations' target-role contacts and their switchboards, and reveal organization details and contact information with the account's credits.\n\nGuides, the error catalog and agent guidance: https://funnelfeedr.com/developers\n\n**Authentication.** An account API key from Settings → API keys, sent as `Authorization: Bearer ff_live_…`. Every key can call every endpoint, the ones that spend credits included, so keep it as safe as a password.\n\n**Batches.** Every lookup and reveal takes 1–25 `items` and answers each one separately, in request order, echoing your `ref`. A bad item gets `status: \"invalid\"`; the rest of the batch still runs.\n\n**Credits.** Only reveals spend: `contacts/reveal`, and `organizations/match`, `people/match` or `organizations/switchboards/match` with `reveal`. Matching is free and returns an organization's basics; its details are a paid reveal. Run them with `dryRun: true` first, always send `maxCredits` and an `Idempotency-Key`, and stop on 402 `insufficient_credits` instead of retrying. Every response that can spend carries `creditsCharged` and `creditsRemaining`.\n\n**Errors.** RFC 9457 `application/problem+json` with a stable `code`; branch on `code` and `retryable`, never on the message.\n\n**Paging.** Only `organizations/contacts/match` pages: `limit` (1–50, default 20) per organization, and each matched result's `nextCursor`, sent back as an item's `cursor`, reads that organization's next page; it is null on the last page.",
    "contact": {
      "name": "Funnelfeedr developers",
      "url": "https://funnelfeedr.com/developers"
    },
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.funnelfeedr.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/external/v1/contacts/reveal": {
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Reveal the email address, phone number or both of up to 25 known contacts, spending the\naccount's credits.",
        "description": "Use this with contact ids from `organizations/contacts/match` or `people/match` (for\nexample an `ambiguous` candidate). Recommended flow for an agent:\n\n1. Call with `dryRun: true`. Nothing is spent; `creditsQuoted` is the price and each\n   result's `reveal` shows what would be unmasked.\n2. If the price is acceptable, call again with `dryRun: false`, `maxCredits` set to\n   that price and a new `Idempotency-Key`.\n3. On 402 `insufficient_credits`, stop and tell the user — do not retry in a loop.\n\nThe reveal is all-or-nothing: every contact is revealed and charged, or nothing is. It fails\nwith 422 `credit_cap_exceeded` when it would cost more than `maxCredits` and with 402\n`insufficient_credits` when the account cannot pay; neither spends anything. Only info\ntypes the account has not already revealed are charged, contacts with none of the requested\ntypes are not charged, and revealed values stay readable to the whole account for a year.\nRetrying with the same `Idempotency-Key` returns the first response instead of charging\nagain.\n\nCost: per contact, email 0.2 and phone 1.0 credits by default (see `x-credit-cost`);\n`linkedIn` is free. Idempotency-Key: required. Rate limit: the spend bucket.",
        "operationId": "reveal_contacts",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Required: this call spends credits. A new unique value (a UUID) per logical request. The first successful response is stored for 24 hours and replayed for a retry with the same key (marked 'Idempotent-Replayed: true'), so a retry can never charge twice. Reusing a key with a different body is 422 idempotency_key_reused.",
            "required": true,
            "schema": {
              "maxLength": 255,
              "type": "string"
            },
            "example": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevealContactsRequest"
              },
              "examples": {
                "dry-run": {
                  "summary": "Step 1: price the reveal; nothing is spent",
                  "value": {
                    "items": [
                      {
                        "ref": "anna",
                        "contactId": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90"
                      },
                      {
                        "ref": "johan",
                        "contactId": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2"
                      }
                    ],
                    "infoTypes": [
                      "email",
                      "phone"
                    ],
                    "dryRun": true
                  }
                },
                "reveal": {
                  "summary": "Step 2: reveal, capped at the quoted price",
                  "value": {
                    "items": [
                      {
                        "ref": "anna",
                        "contactId": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90"
                      },
                      {
                        "ref": "johan",
                        "contactId": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2"
                      }
                    ],
                    "infoTypes": [
                      "email",
                      "phone"
                    ],
                    "maxCredits": 1.4,
                    "dryRun": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per contact, in request order, with the request's credit totals.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              },
              "Funnelfeedr-Credits-Remaining": {
                "description": "The account's credit balance after this request. Absent when the account has no credit subscription.",
                "schema": {
                  "type": "number"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevealContactsResponse"
                },
                "examples": {
                  "quote": {
                    "summary": "Dry run: per-contact prices and the total; contacts still masked",
                    "value": {
                      "results": [
                        {
                          "ref": "anna",
                          "status": "quoted",
                          "contactId": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                          "reveal": {
                            "creditCost": 1.2,
                            "newInfoTypes": [
                              "email",
                              "phone"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": []
                          },
                          "contact": {
                            "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                            "name": "Anna Svensson-Berg",
                            "jobTitle": "Head of Procurement",
                            "isExecutive": false,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "XXXXX@nordviklogistik.se",
                            "emailIsMasked": true,
                            "phone": "+46701XXXXXX",
                            "phoneIsMasked": true,
                            "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                          }
                        },
                        {
                          "ref": "johan",
                          "status": "quoted",
                          "contactId": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2",
                          "reveal": {
                            "creditCost": 0.2,
                            "newInfoTypes": [
                              "email"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": [
                              "phone"
                            ]
                          },
                          "contact": {
                            "id": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2",
                            "name": "Johan Lindqvist",
                            "jobTitle": "CFO",
                            "isExecutive": true,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "XXXXX@nordviklogistik.se",
                            "emailIsMasked": true,
                            "phoneIsMasked": false
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 1.4,
                      "creditsRemaining": 412.4,
                      "dryRun": true
                    }
                  },
                  "revealed": {
                    "summary": "Revealed and charged; a contact revealed before costs nothing again",
                    "value": {
                      "results": [
                        {
                          "ref": "anna",
                          "status": "revealed",
                          "contactId": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                          "reveal": {
                            "creditCost": 0,
                            "newInfoTypes": [],
                            "alreadyRevealedInfoTypes": [
                              "email",
                              "phone"
                            ],
                            "unavailableInfoTypes": []
                          },
                          "contact": {
                            "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                            "name": "Anna Svensson-Berg",
                            "jobTitle": "Head of Procurement",
                            "isExecutive": false,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "anna.svensson-berg@nordviklogistik.se",
                            "emailIsMasked": false,
                            "phone": "+46701234567",
                            "phoneIsMasked": false,
                            "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                          }
                        },
                        {
                          "ref": "missing",
                          "status": "not_found",
                          "contactId": "e3b0c442-98fc-4c14-9afb-f4c8996fb924"
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 411.2,
                      "dryRun": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — An Idempotency-Key header is required",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_required": {
                    "summary": "Spend call without Idempotency-Key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_required",
                      "title": "An Idempotency-Key header is required",
                      "status": 400,
                      "detail": "This call spends credits. Send an Idempotency-Key header (a new UUID per logical request) so a retry cannot charge twice.",
                      "code": "idempotency_key_required",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_credits` — Not enough credits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "insufficient_credits": {
                    "summary": "Account cannot pay",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/insufficient_credits",
                      "title": "Not enough credits",
                      "status": 402,
                      "detail": "This request needs 3.4 credits; the account has 1.2. Nothing was revealed or charged. Buy credits in Funnelfeedr under Settings → Subscription, then retry.",
                      "code": "insufficient_credits",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "creditsRemaining": 1.2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use` — A request with this Idempotency-Key is still in progress",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_in_use": {
                    "summary": "Same key still running",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_in_use",
                      "title": "A request with this Idempotency-Key is still in progress",
                      "status": 409,
                      "detail": "A request with this Idempotency-Key is still running. Retry shortly to get its response.",
                      "code": "idempotency_key_in_use",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — The request body must be JSON",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "Body is not JSON",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/unsupported_media_type",
                      "title": "The request body must be JSON",
                      "status": 415,
                      "detail": "Send the request body as JSON, with 'Content-Type: application/json'.",
                      "code": "unsupported_media_type",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reused` — Idempotency-Key was already used with a different request; `validation_failed` — The request is invalid; `too_many_items` — Too many items in one request; `credit_cap_exceeded` — The request would cost more than maxCredits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_reused": {
                    "summary": "Key reused with another body",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_reused",
                      "title": "Idempotency-Key was already used with a different request",
                      "status": 422,
                      "detail": "This Idempotency-Key was already used with a different request. Use a new key for a new request.",
                      "code": "idempotency_key_reused",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "validation_failed": {
                    "summary": "Request invalid",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/validation_failed",
                      "title": "The request is invalid",
                      "status": 422,
                      "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
                      "code": "validation_failed",
                      "retryable": false,
                      "errors": {
                        "maxCredits": [
                          "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
                        ]
                      },
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "too_many_items": {
                    "summary": "More than 25 items",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/too_many_items",
                      "title": "Too many items in one request",
                      "status": 422,
                      "detail": "This request has 40 items; the limit is 25. Split it into several requests.",
                      "code": "too_many_items",
                      "retryable": false,
                      "maxItems": 25,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "credit_cap_exceeded": {
                    "summary": "Would cost more than maxCredits",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/credit_cap_exceeded",
                      "title": "The request would cost more than maxCredits",
                      "status": 422,
                      "detail": "This reveal costs 3.4 credits, more than maxCredits (2). Nothing was revealed or charged. Raise maxCredits or reveal fewer contacts or info types.",
                      "code": "credit_cap_exceeded",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "maxCredits": 2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "x-credit-cost": {
          "unit": "credits per contact",
          "spendsWhen": "unless `dryRun` is true",
          "defaults": {
            "email": 0.2,
            "phone": 1,
            "linkedIn": 0
          },
          "rules": [
            "Prices are per contact and info type; email and phone together cost the sum.",
            "An info type the account has already revealed (within the last year) costs nothing again.",
            "A contact with none of the requested info types is not charged.",
            "A contact that appears more than once in a request is charged once; each of its results shows that one price, so add up creditsQuoted, not the results.",
            "An account group can have its own prices; GET /external/v1/credits shows the balance, not prices.",
            "A request is charged all-or-nothing, and never more than maxCredits."
          ]
        }
      }
    },
    "/external/v1/credits": {
      "get": {
        "tags": [
          "Credits"
        ],
        "summary": "Get the account's credit balance.",
        "description": "Free. Reveals spend included credits first, then\npurchased ones. The total is also returned in the `Funnelfeedr-Credits-Remaining`\nheader, as on every response that can spend.",
        "operationId": "get_credits",
        "responses": {
          "200": {
            "description": "The balance.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              },
              "Funnelfeedr-Credits-Remaining": {
                "description": "The account's credit balance after this request. Absent when the account has no credit subscription.",
                "schema": {
                  "type": "number"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalCreditBalanceModel"
                },
                "examples": {
                  "balance": {
                    "summary": "Included credits are spent before purchased ones",
                    "value": {
                      "included": 300,
                      "purchased": 112.4,
                      "total": 412.4
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/external/v1/organizations/contacts/match": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "The people at each organization who match your account's target contact roles, ranked as the\nweb app ranks them — up to 25 organizations per call.",
        "description": "Use this to see who you would want to reach at the organizations in your CRM, spreadsheet or\nprompt before revealing anyone. Identify up to 25 organizations exactly as in\n`organizations/match` (same fields, same `id` → `orgNumber` →\n`linkedInUrl` → `domain` precedence). Each comes back with its own `status`, in\nrequest order, with your `ref` echoed:\n\n- `matched` — `organization` (the basics, plus its details only if the account has\n  revealed them with `organizations/match`) and one page of its `contacts`, with\n  `totalCount`, `filteredByTargetRoles` and `nextCursor`.\n- `not_found` — `reason` is `organization_not_found`: no organization matches the\n  identifiers. Free.\n- `invalid` — the item is unusable (no identifier, a malformed one, a cursor that was\n  altered or belongs to another organization); see `error`.\n\n**Which contacts.** Only the contacts that match the target contact roles set up for your\naccount in Funnelfeedr (Settings → Target contact roles) — the same contacts the web app shows\nunder the \"Target roles\" filter of the organization's contacts tab. When the account has no\ntarget contact roles set up, the web app has no such filter and lists every contact; so does\nthis endpoint, and `filteredByTargetRoles` is then false. Contacts that come from your\nconnected CRM carry no target-role verdict in Funnelfeedr, so they are left out while the\nfilter applies. Email and phone are masked (`XXXXX@volvo.se`) until the account reveals\nthem; name, title and LinkedIn never are. To unmask, pass the contact ids you want to\n`contacts/reveal` — start with `dryRun: true` to see the price.\n\n**Paging.** `limit` (1–50, default 20) is the page size for every organization in the\ncall. A result's `nextCursor` is null on the organization's last page; otherwise send it\nback as an item's `cursor` — alone, or beside the same identifiers — to read the next page.\nOne call can mix first pages and next pages of different organizations.\n\nCost: free. Rate limit: the search bucket, once per call however many organizations it names.",
        "operationId": "match_organization_contacts",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchOrganizationContactsRequest"
              },
              "examples": {
                "batch": {
                  "summary": "Two organizations from a CRM export, two contacts each per page",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      },
                      {
                        "ref": "crm-1002",
                        "orgNumber": "5590123456",
                        "countryCode": "SE"
                      }
                    ],
                    "limit": 2
                  }
                },
                "next-page": {
                  "summary": "The first organization's next page: its nextCursor, no identifier needed",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "cursor": "eyJzIjoib3JnYW5pemF0aW9uLWNvbnRhY3RzOjZmOWQyZjFlNmYzNzRhMmY5YTNlMWI1YzBmNGQ3ZTIxIiwicCI6MiwibiI6Mn0"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per organization, in request order.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchOrganizationContactsResponse"
                },
                "examples": {
                  "matched": {
                    "summary": "Both organizations found: a first page of target-role contacts each, email and phone masked until revealed. The second organization has none.",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contacts": [
                            {
                              "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                              "name": "Anna Svensson-Berg",
                              "jobTitle": "Head of Procurement",
                              "isExecutive": false,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phone": "+46701XXXXXX",
                              "phoneIsMasked": true,
                              "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                            },
                            {
                              "id": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2",
                              "name": "Johan Lindqvist",
                              "jobTitle": "CFO",
                              "isExecutive": true,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phoneIsMasked": false
                            }
                          ],
                          "totalCount": 3,
                          "filteredByTargetRoles": true,
                          "nextCursor": "eyJzIjoib3JnYW5pemF0aW9uLWNvbnRhY3RzOjZmOWQyZjFlNmYzNzRhMmY5YTNlMWI1YzBmNGQ3ZTIxIiwicCI6MiwibiI6Mn0"
                        },
                        {
                          "ref": "crm-1002",
                          "status": "matched",
                          "matchedBy": "orgNumber",
                          "organization": {
                            "id": "2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21",
                            "name": "Fjällräven Frakt AB",
                            "detailsRevealed": false,
                            "organizationNumber": "559012-3456",
                            "countryCode": "SE",
                            "city": "Stockholm",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2017,
                            "url": "https://app.funnelfeedr.com/organizations/2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21"
                          },
                          "contacts": [],
                          "totalCount": 0,
                          "filteredByTargetRoles": true,
                          "nextCursor": null
                        }
                      ]
                    }
                  },
                  "next-page": {
                    "summary": "The first organization's last page, read by its cursor alone (so no matchedBy)",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contacts": [
                            {
                              "id": "7c4e2a91-1d6b-4f38-a5e0-3b9c8d2f6a17",
                              "name": "Erik Holm",
                              "jobTitle": "Purchasing Manager",
                              "isExecutive": false,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phoneIsMasked": false
                            }
                          ],
                          "totalCount": 3,
                          "filteredByTargetRoles": true,
                          "nextCursor": null
                        }
                      ]
                    }
                  },
                  "not-found-and-invalid": {
                    "summary": "An unknown organization, an item with no identifier and an altered cursor. Free.",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1003",
                          "status": "not_found",
                          "reason": "organization_not_found"
                        },
                        {
                          "ref": "crm-1004",
                          "status": "invalid",
                          "error": {
                            "code": "missing_identifier",
                            "message": "Give one of id, orgNumber, linkedInUrl or domain."
                          }
                        },
                        {
                          "ref": "crm-1005",
                          "status": "invalid",
                          "error": {
                            "code": "invalid_cursor",
                            "message": "cursor is not one this endpoint returned. Send nextCursor back exactly as it was returned, or leave cursor out for the first page."
                          }
                        }
                      ]
                    }
                  },
                  "no-target-roles": {
                    "summary": "The account has no target contact roles set up: every contact, as the web app lists them",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contacts": [
                            {
                              "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                              "name": "Anna Svensson-Berg",
                              "jobTitle": "Head of Procurement",
                              "isExecutive": false,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phone": "+46701XXXXXX",
                              "phoneIsMasked": true,
                              "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                            },
                            {
                              "id": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2",
                              "name": "Johan Lindqvist",
                              "jobTitle": "CFO",
                              "isExecutive": true,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phoneIsMasked": false
                            },
                            {
                              "id": "e3b0c442-98fc-4c14-9afb-f4c8996fb924",
                              "name": "Anna Svensson",
                              "jobTitle": "Warehouse Manager",
                              "isExecutive": false,
                              "isDecisionMaker": false,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "emailIsMasked": false,
                              "phone": "+46702XXXXXX",
                              "phoneIsMasked": true
                            },
                            {
                              "id": "7c4e2a91-1d6b-4f38-a5e0-3b9c8d2f6a17",
                              "name": "Erik Holm",
                              "jobTitle": "Purchasing Manager",
                              "isExecutive": false,
                              "isDecisionMaker": true,
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB",
                              "email": "XXXXX@nordviklogistik.se",
                              "emailIsMasked": true,
                              "phoneIsMasked": false
                            }
                          ],
                          "totalCount": 4,
                          "filteredByTargetRoles": false,
                          "nextCursor": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — The request body must be JSON",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "Body is not JSON",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/unsupported_media_type",
                      "title": "The request body must be JSON",
                      "status": 415,
                      "detail": "Send the request body as JSON, with 'Content-Type: application/json'.",
                      "code": "unsupported_media_type",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`validation_failed` — The request is invalid; `too_many_items` — Too many items in one request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "validation_failed": {
                    "summary": "Request invalid",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/validation_failed",
                      "title": "The request is invalid",
                      "status": 422,
                      "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
                      "code": "validation_failed",
                      "retryable": false,
                      "errors": {
                        "maxCredits": [
                          "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
                        ]
                      },
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "too_many_items": {
                    "summary": "More than 25 items",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/too_many_items",
                      "title": "Too many items in one request",
                      "status": 422,
                      "detail": "This request has 40 items; the limit is 25. Split it into several requests.",
                      "code": "too_many_items",
                      "retryable": false,
                      "maxItems": 25,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/external/v1/organizations/match": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Find organizations you can already identify, by Funnelfeedr id, website domain, national\norganization number or LinkedIn page — and optionally reveal their details in the same call.",
        "description": "Use this to turn the organizations in your CRM, spreadsheet or prompt into Funnelfeedr\norganizations — before reading their contacts, finding their switchboards or matching people\nat them. Up to 25 lookups per call; each comes back with its own `status`, in request\norder, with your `ref` echoed:\n\n- `matched` — `organization` holds the organization; `matchedBy` says which\n  identifier hit.\n- `not_found` — Funnelfeedr does not know the organization. Free; nothing is created and\n  no enrichment is started.\n- `invalid` — the item is unusable (no identifier, a malformed one); see `error`.\n\nGive one identifier per item, or several to fall back: they are tried in the order\n`id`, `orgNumber`, `linkedInUrl`, `domain`. A domain matches only the\norganization that owns the site, so a subsidiary sharing its parent's website is not\nreturned for the parent's domain — use its organization number. Organization names are not\naccepted: a name is ambiguous, and a silently wrong organization is worse than a miss.\n\n**Basics and details.** A match is free and returns the basics: `id`, `url`,\n`name`, `countryCode`, `legalForm`, `city`, `foundedYear`, and the\nidentifier you matched by (an `orgNumber` match returns `organizationNumber`, a\n`domain` match `domain`, a `linkedInUrl` match `linkedInUrl`; an\n`id` match nothing extra). Everything else is a detail: the identifiers you did not send,\nwebsite, employees, revenue, descriptions, technologies, keywords, tags and the full address.\nAdd `reveal: [\"details\"]` to reveal the details of every matched organization in the same\ncall. `organization.detailsRevealed` says whether an organization's details are shown; an\norganization the account has revealed within the last year returns them on every match,\nfree.\n\nA reveal that is not a dry run spends credits, so it then also needs an\n`Idempotency-Key` header and `maxCredits`. The charge is one all-or-nothing reveal\nover all matched organizations not revealed before: if it would exceed `maxCredits` the\ncall fails with 422 `credit_cap_exceeded`, and if the account cannot pay with 402\n`insufficient_credits` — in both cases nothing is spent and no match is returned, so\nstart with `dryRun: true` when unsure. `not_found` and `invalid` items, and an\norganization with no details beyond the basics, are never charged.\n\nCost: free without `reveal`; with it, 0.2 credits per matched organization by default\n(see `x-credit-cost`). Rate limit: the search bucket, plus the spend bucket when it\nspends.",
        "operationId": "match_organizations",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Required whenever this call spends credits; optional otherwise. A new unique value (a UUID) per logical request. The first successful response is stored for 24 hours and replayed for a retry with the same key (marked 'Idempotent-Replayed: true'), so a retry can never charge twice. Reusing a key with a different body is 422 idempotency_key_reused.",
            "schema": {
              "maxLength": 255,
              "type": "string"
            },
            "example": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchOrganizationsRequest"
              },
              "examples": {
                "batch": {
                  "summary": "Three organizations from a CRM export, each with its own identifier",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      },
                      {
                        "ref": "crm-1002",
                        "orgNumber": "5590123456",
                        "countryCode": "SE"
                      },
                      {
                        "ref": "crm-1003",
                        "domain": "unknown-startup.se"
                      }
                    ],
                    "dryRun": false
                  }
                },
                "fallback": {
                  "summary": "Several identifiers for one organization: tried id, orgNumber, linkedInUrl, domain",
                  "value": {
                    "items": [
                      {
                        "ref": "row-7",
                        "domain": "nordviklogistik.se",
                        "orgNumber": "556677-8899",
                        "countryCode": "SE"
                      }
                    ],
                    "dryRun": false
                  }
                },
                "dry-run-reveal": {
                  "summary": "Match and price revealing the details without spending",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      }
                    ],
                    "reveal": [
                      "details"
                    ],
                    "dryRun": true
                  }
                },
                "reveal": {
                  "summary": "Match and reveal the details, capped at 0.2 credits (needs an Idempotency-Key)",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      }
                    ],
                    "reveal": [
                      "details"
                    ],
                    "maxCredits": 0.2,
                    "dryRun": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per item, in request order, with the request's credit totals.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              },
              "Funnelfeedr-Credits-Remaining": {
                "description": "The account's credit balance after this request. Absent when the account has no credit subscription.",
                "schema": {
                  "type": "number"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchOrganizationsResponse"
                },
                "examples": {
                  "batch": {
                    "summary": "One hit by domain, one by organization number, one miss. Free: the basics and the identifier sent, details held back",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          }
                        },
                        {
                          "ref": "crm-1002",
                          "status": "matched",
                          "matchedBy": "orgNumber",
                          "organization": {
                            "id": "2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21",
                            "name": "Fjällräven Frakt AB",
                            "detailsRevealed": false,
                            "organizationNumber": "559012-3456",
                            "countryCode": "SE",
                            "city": "Stockholm",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2017,
                            "url": "https://app.funnelfeedr.com/organizations/2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21"
                          }
                        },
                        {
                          "ref": "crm-1003",
                          "status": "not_found"
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  },
                  "dry-run": {
                    "summary": "Dry run: what revealing the details would cost; nothing spent",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "creditCost": 0.2
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0.2,
                      "creditsRemaining": 412.4,
                      "dryRun": true
                    }
                  },
                  "revealed": {
                    "summary": "Matched and revealed: every detail, charged once; free on later matches for a year",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": true,
                            "organizationNumber": "556677-8899",
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "website": "https://www.nordviklogistik.se",
                            "linkedInUrl": "https://www.linkedin.com/company/nordvik-logistik",
                            "city": "Göteborg",
                            "addressLine1": "Hamngatan 12",
                            "postalCode": "411 06",
                            "employeesMin": 50,
                            "employeesMax": 99,
                            "revenueMin": 100000000,
                            "revenueMax": 250000000,
                            "revenueCurrency": "SEK",
                            "oneSentenceDescription": "Nordvik Logistik runs third-party warehousing and road freight for retailers in western Sweden.",
                            "description": "Nordvik Logistik AB is a Gothenburg logistics provider offering warehousing, order fulfilment and domestic road freight to retail and e-commerce customers across western Sweden.",
                            "technologies": [
                              "Google Analytics",
                              "HubSpot",
                              "WordPress"
                            ],
                            "keywords": [
                              "3PL",
                              "road freight",
                              "warehousing"
                            ],
                            "tags": [
                              "Logistics",
                              "B2B"
                            ],
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "creditCost": 0.2
                        }
                      ],
                      "creditsCharged": 0.2,
                      "creditsQuoted": 0.2,
                      "creditsRemaining": 412.2,
                      "dryRun": false
                    }
                  },
                  "invalid-item": {
                    "summary": "An unusable item is reported on its own; the rest of the batch still runs",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-2001",
                          "status": "invalid",
                          "error": {
                            "code": "invalid_value",
                            "message": "linkedInUrl must be an organization's LinkedIn page, https://www.linkedin.com/company/…"
                          }
                        },
                        {
                          "ref": "crm-2002",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — An Idempotency-Key header is required",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_required": {
                    "summary": "Spend call without Idempotency-Key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_required",
                      "title": "An Idempotency-Key header is required",
                      "status": 400,
                      "detail": "This call spends credits. Send an Idempotency-Key header (a new UUID per logical request) so a retry cannot charge twice.",
                      "code": "idempotency_key_required",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_credits` — Not enough credits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "insufficient_credits": {
                    "summary": "Account cannot pay",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/insufficient_credits",
                      "title": "Not enough credits",
                      "status": 402,
                      "detail": "This request needs 3.4 credits; the account has 1.2. Nothing was revealed or charged. Buy credits in Funnelfeedr under Settings → Subscription, then retry.",
                      "code": "insufficient_credits",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "creditsRemaining": 1.2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use` — A request with this Idempotency-Key is still in progress",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_in_use": {
                    "summary": "Same key still running",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_in_use",
                      "title": "A request with this Idempotency-Key is still in progress",
                      "status": 409,
                      "detail": "A request with this Idempotency-Key is still running. Retry shortly to get its response.",
                      "code": "idempotency_key_in_use",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — The request body must be JSON",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "Body is not JSON",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/unsupported_media_type",
                      "title": "The request body must be JSON",
                      "status": 415,
                      "detail": "Send the request body as JSON, with 'Content-Type: application/json'.",
                      "code": "unsupported_media_type",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reused` — Idempotency-Key was already used with a different request; `validation_failed` — The request is invalid; `too_many_items` — Too many items in one request; `credit_cap_exceeded` — The request would cost more than maxCredits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_reused": {
                    "summary": "Key reused with another body",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_reused",
                      "title": "Idempotency-Key was already used with a different request",
                      "status": 422,
                      "detail": "This Idempotency-Key was already used with a different request. Use a new key for a new request.",
                      "code": "idempotency_key_reused",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "validation_failed": {
                    "summary": "Request invalid",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/validation_failed",
                      "title": "The request is invalid",
                      "status": 422,
                      "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
                      "code": "validation_failed",
                      "retryable": false,
                      "errors": {
                        "maxCredits": [
                          "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
                        ]
                      },
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "too_many_items": {
                    "summary": "More than 25 items",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/too_many_items",
                      "title": "Too many items in one request",
                      "status": 422,
                      "detail": "This request has 40 items; the limit is 25. Split it into several requests.",
                      "code": "too_many_items",
                      "retryable": false,
                      "maxItems": 25,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "credit_cap_exceeded": {
                    "summary": "Would cost more than maxCredits",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/credit_cap_exceeded",
                      "title": "The request would cost more than maxCredits",
                      "status": 422,
                      "detail": "This reveal costs 3.4 credits, more than maxCredits (2). Nothing was revealed or charged. Raise maxCredits or reveal fewer contacts or info types.",
                      "code": "credit_cap_exceeded",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "maxCredits": 2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "x-credit-cost": {
          "unit": "credits per organization",
          "spendsWhen": "when `reveal` is set and `dryRun` is false",
          "defaults": {
            "details": 0.2
          },
          "rules": [
            "Prices are per matched organization: one price for all of its details.",
            "An organization the account has already revealed (within the last year) costs nothing again, and its details come back free on every match.",
            "An organization with no details beyond the free basics is not charged.",
            "An organization that appears more than once in a request is charged once; each of its results shows that one price, so add up creditsQuoted, not the results.",
            "An account group can have its own prices; GET /external/v1/credits shows the balance, not prices.",
            "A request is charged all-or-nothing, and never more than maxCredits."
          ]
        }
      }
    },
    "/external/v1/organizations/switchboards/match": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Find each organization's switchboard — its main phone line rather than a person — and\noptionally reveal its number and email in the same call.",
        "description": "Use this when you want to call an organization but not a named person: a switchboard,\nreception or general information line. Identify up to 25 organizations exactly as in\n`organizations/match` (same fields, same `id` → `orgNumber` →\n`linkedInUrl` → `domain` precedence). Each comes back with its own `status`, in\nrequest order, with your `ref` echoed. `organization` carries the basics, plus its\ndetails only if the account has revealed them with `organizations/match`; this endpoint\nnever reveals organization details:\n\n- `matched` — `contact` is the switchboard: the organization's contact that is not a\n  person and has a phone number, picked the way the Funnelfeedr dialer picks \"the\n  switchboard\" (the best-ranked such contact, then a number in the organization's own\n  country). Its phone, and its email when it has one, are masked unless already revealed, or\n  revealed by this call.\n- `not_found` — `reason` is `organization_not_found` (no organization matches\n  the identifiers) or `no_switchboard` (the organization is known, and returned in\n  `organization`, but has no switchboard with a phone number). Free.\n- `invalid` — the item is unusable; see `error`.\n\n**Revealing.** Finding switchboards is free. Add `reveal: [\"phone\"]`,\n`[\"email\"]` or both to unmask them on every matched switchboard in the same call. A reveal that is not a dry run spends credits,\nso it then also needs an `Idempotency-Key` header and `maxCredits`. The charge is one all-or-nothing reveal\nover all matched switchboards: if it would exceed `maxCredits` the call fails with 422\n`credit_cap_exceeded`, and if the account cannot pay with 402\n`insufficient_credits` — in both cases nothing is spent and no switchboard is returned,\nso start with `dryRun: true` when unsure. A phone or email the account has revealed before\nis not charged again.\n\nCost: free without `reveal`; with it, by default 1.0 credit per switchboard phone and 0.2\nper switchboard email (see `x-credit-cost`). Rate limit: the search bucket, plus the spend bucket when it spends.",
        "operationId": "match_organization_switchboards",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Required whenever this call spends credits; optional otherwise. A new unique value (a UUID) per logical request. The first successful response is stored for 24 hours and replayed for a retry with the same key (marked 'Idempotent-Replayed: true'), so a retry can never charge twice. Reusing a key with a different body is 422 idempotency_key_reused.",
            "schema": {
              "maxLength": 255,
              "type": "string"
            },
            "example": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchOrganizationSwitchboardsRequest"
              },
              "examples": {
                "find": {
                  "summary": "Find two organizations' switchboards (free, no reveal)",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      },
                      {
                        "ref": "crm-1002",
                        "orgNumber": "5590123456",
                        "countryCode": "SE"
                      }
                    ],
                    "dryRun": false
                  }
                },
                "dry-run-reveal": {
                  "summary": "Find and price revealing the switchboard number without spending",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      }
                    ],
                    "reveal": [
                      "phone"
                    ],
                    "dryRun": true
                  }
                },
                "reveal": {
                  "summary": "Find and reveal, capped at 1 credit (needs an Idempotency-Key)",
                  "value": {
                    "items": [
                      {
                        "ref": "crm-1001",
                        "domain": "nordviklogistik.se"
                      }
                    ],
                    "reveal": [
                      "phone"
                    ],
                    "maxCredits": 1,
                    "dryRun": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per organization, in request order, with the request's credit totals.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              },
              "Funnelfeedr-Credits-Remaining": {
                "description": "The account's credit balance after this request. Absent when the account has no credit subscription.",
                "schema": {
                  "type": "number"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchOrganizationSwitchboardsResponse"
                },
                "examples": {
                  "found": {
                    "summary": "One switchboard found, number still masked; the other organization has none. Free.",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contact": {
                            "id": "5a1c9e7f-3b2d-4c6e-8f0a-9d4b7e2c1f38",
                            "name": "Nordvik Logistik AB",
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "emailIsMasked": false,
                            "phone": "+46317XXXXXX",
                            "phoneIsMasked": true
                          }
                        },
                        {
                          "ref": "crm-1002",
                          "status": "not_found",
                          "reason": "no_switchboard",
                          "matchedBy": "orgNumber",
                          "organization": {
                            "id": "2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21",
                            "name": "Fjällräven Frakt AB",
                            "detailsRevealed": false,
                            "organizationNumber": "559012-3456",
                            "countryCode": "SE",
                            "city": "Stockholm",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2017,
                            "url": "https://app.funnelfeedr.com/organizations/2b7e1c55-0d3a-4f7e-9c1b-8a6d5e4f3a21"
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  },
                  "dry-run": {
                    "summary": "Dry run: what revealing the number would cost; nothing spent",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contact": {
                            "id": "5a1c9e7f-3b2d-4c6e-8f0a-9d4b7e2c1f38",
                            "name": "Nordvik Logistik AB",
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "emailIsMasked": false,
                            "phone": "+46317XXXXXX",
                            "phoneIsMasked": true
                          },
                          "reveal": {
                            "creditCost": 1,
                            "newInfoTypes": [
                              "phone"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": []
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 1,
                      "creditsRemaining": 412.4,
                      "dryRun": true
                    }
                  },
                  "revealed": {
                    "summary": "Found and revealed: the number unmasked and charged",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1001",
                          "status": "matched",
                          "matchedBy": "domain",
                          "organization": {
                            "id": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "name": "Nordvik Logistik AB",
                            "detailsRevealed": false,
                            "countryCode": "SE",
                            "domain": "nordviklogistik.se",
                            "city": "Göteborg",
                            "legalForm": "Aktiebolag",
                            "foundedYear": 2004,
                            "url": "https://app.funnelfeedr.com/organizations/6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21"
                          },
                          "contact": {
                            "id": "5a1c9e7f-3b2d-4c6e-8f0a-9d4b7e2c1f38",
                            "name": "Nordvik Logistik AB",
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "emailIsMasked": false,
                            "phone": "+46317001234",
                            "phoneIsMasked": false
                          },
                          "reveal": {
                            "creditCost": 1,
                            "newInfoTypes": [
                              "phone"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": []
                          }
                        }
                      ],
                      "creditsCharged": 1,
                      "creditsQuoted": 1,
                      "creditsRemaining": 411.4,
                      "dryRun": false
                    }
                  },
                  "not-found": {
                    "summary": "An unknown organization and an unusable item. Free.",
                    "value": {
                      "results": [
                        {
                          "ref": "crm-1003",
                          "status": "not_found",
                          "reason": "organization_not_found"
                        },
                        {
                          "ref": "crm-1004",
                          "status": "invalid",
                          "error": {
                            "code": "missing_identifier",
                            "message": "Give one of id, orgNumber, linkedInUrl or domain."
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — An Idempotency-Key header is required",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_required": {
                    "summary": "Spend call without Idempotency-Key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_required",
                      "title": "An Idempotency-Key header is required",
                      "status": 400,
                      "detail": "This call spends credits. Send an Idempotency-Key header (a new UUID per logical request) so a retry cannot charge twice.",
                      "code": "idempotency_key_required",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_credits` — Not enough credits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "insufficient_credits": {
                    "summary": "Account cannot pay",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/insufficient_credits",
                      "title": "Not enough credits",
                      "status": 402,
                      "detail": "This request needs 3.4 credits; the account has 1.2. Nothing was revealed or charged. Buy credits in Funnelfeedr under Settings → Subscription, then retry.",
                      "code": "insufficient_credits",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "creditsRemaining": 1.2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use` — A request with this Idempotency-Key is still in progress",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_in_use": {
                    "summary": "Same key still running",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_in_use",
                      "title": "A request with this Idempotency-Key is still in progress",
                      "status": 409,
                      "detail": "A request with this Idempotency-Key is still running. Retry shortly to get its response.",
                      "code": "idempotency_key_in_use",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — The request body must be JSON",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "Body is not JSON",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/unsupported_media_type",
                      "title": "The request body must be JSON",
                      "status": 415,
                      "detail": "Send the request body as JSON, with 'Content-Type: application/json'.",
                      "code": "unsupported_media_type",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reused` — Idempotency-Key was already used with a different request; `validation_failed` — The request is invalid; `too_many_items` — Too many items in one request; `credit_cap_exceeded` — The request would cost more than maxCredits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_reused": {
                    "summary": "Key reused with another body",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_reused",
                      "title": "Idempotency-Key was already used with a different request",
                      "status": 422,
                      "detail": "This Idempotency-Key was already used with a different request. Use a new key for a new request.",
                      "code": "idempotency_key_reused",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "validation_failed": {
                    "summary": "Request invalid",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/validation_failed",
                      "title": "The request is invalid",
                      "status": 422,
                      "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
                      "code": "validation_failed",
                      "retryable": false,
                      "errors": {
                        "maxCredits": [
                          "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
                        ]
                      },
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "too_many_items": {
                    "summary": "More than 25 items",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/too_many_items",
                      "title": "Too many items in one request",
                      "status": 422,
                      "detail": "This request has 40 items; the limit is 25. Split it into several requests.",
                      "code": "too_many_items",
                      "retryable": false,
                      "maxItems": 25,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "credit_cap_exceeded": {
                    "summary": "Would cost more than maxCredits",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/credit_cap_exceeded",
                      "title": "The request would cost more than maxCredits",
                      "status": 422,
                      "detail": "This reveal costs 3.4 credits, more than maxCredits (2). Nothing was revealed or charged. Raise maxCredits or reveal fewer contacts or info types.",
                      "code": "credit_cap_exceeded",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "maxCredits": 2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "x-credit-cost": {
          "unit": "credits per switchboard",
          "spendsWhen": "when `reveal` is set and `dryRun` is false",
          "defaults": {
            "email": 0.2,
            "phone": 1
          },
          "rules": [
            "Prices are per switchboard and info type, at the non-person rates; email and phone together cost the sum.",
            "An info type the account has already revealed (within the last year) costs nothing again.",
            "A switchboard without the requested info type is not charged for it.",
            "A switchboard that appears more than once in a request is charged once; each of its results shows that one price, so add up creditsQuoted, not the results.",
            "An account group can have its own prices; GET /external/v1/credits shows the balance, not prices.",
            "A request is charged all-or-nothing, and never more than maxCredits."
          ]
        }
      }
    },
    "/external/v1/people/match": {
      "post": {
        "tags": [
          "People"
        ],
        "summary": "Find people by LinkedIn profile, email address, or name plus organization — and optionally reveal\ntheir email and phone in the same call.",
        "description": "Use this when you know who you are looking for: a lead from a form, a name from a meeting, a\nrow in a CRM. Up to 25 people per call, each answered with its own `status`, in request\norder, with your `ref` echoed:\n\n- `matched` — exactly one contact; `contact` holds it (email and phone masked unless\n  already revealed, or revealed by this call).\n- `ambiguous` — several contacts fit; up to 5 `candidates`. Nothing is revealed or\n  charged for them. Pick one and call `contacts/reveal` with its id.\n- `not_found` — `reason` is `organization_not_found` (the organization matched nothing)\n  or `person_not_found`. Free.\n- `invalid` — the item is unusable; see `error`.\n\nName matching is deterministic, not fuzzy: case, accents (Åsa = Asa) and hyphenated surnames\n(Svensson = Svensson-Berg) are normalised; nicknames (Kalle for Karl) and typos do not match.\n\nAn `email` only matches an address your account already sees unmasked — one it has\nrevealed before, or one that belongs to your account. A masked\naddress is `not_found` with `person_not_found`: matching is not a way to check\nwhether a guessed email exists. To find a person whose email you only suspect, match by\n`linkedInUrl` or by `name` plus `organization`.\n\n**Revealing.** Matching alone is free. Add `reveal` (any of `email`, `phone`,\n`linkedIn`) to unmask every matched person in the same call. A reveal that is not a dry\nrun spends credits, so it then also needs an `Idempotency-Key` header and\n`maxCredits`. The charge is one\nall-or-nothing reveal over all matched people: if it would exceed `maxCredits` the call\nfails with 422 `credit_cap_exceeded`, and if the account cannot pay with 402\n`insufficient_credits` — in both cases nothing is spent and no match is returned, so\nretry with `dryRun: true` first when unsure. Only info types the account has not revealed\nbefore are charged, and only for `matched` people.\n\nCost: free without `reveal`; with it, per matched person, email 0.2 and phone 1.0 credits\nby default (see `x-credit-cost`). Rate limit: the search bucket, plus the spend bucket\nwhen it spends.",
        "operationId": "match_people",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Required whenever this call spends credits; optional otherwise. A new unique value (a UUID) per logical request. The first successful response is stored for 24 hours and replayed for a retry with the same key (marked 'Idempotent-Replayed: true'), so a retry can never charge twice. Reusing a key with a different body is 422 idempotency_key_reused.",
            "schema": {
              "maxLength": 255,
              "type": "string"
            },
            "example": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchPeopleRequest"
              },
              "examples": {
                "name-and-domain": {
                  "summary": "Match by name + organization domain (free, no reveal)",
                  "value": {
                    "items": [
                      {
                        "ref": "lead-42",
                        "name": "Anna Svensson",
                        "organization": {
                          "domain": "nordviklogistik.se"
                        }
                      }
                    ],
                    "dryRun": false
                  }
                },
                "dry-run-reveal": {
                  "summary": "Match and price a reveal of email and phone without spending",
                  "value": {
                    "items": [
                      {
                        "ref": "lead-42",
                        "name": "Anna Svensson",
                        "organization": {
                          "domain": "nordviklogistik.se"
                        }
                      },
                      {
                        "ref": "lead-43",
                        "linkedInUrl": "https://www.linkedin.com/in/johan-lindqvist-cfo"
                      }
                    ],
                    "reveal": [
                      "email",
                      "phone"
                    ],
                    "dryRun": true
                  }
                },
                "reveal": {
                  "summary": "Match and reveal, capped at 2 credits (needs an Idempotency-Key)",
                  "value": {
                    "items": [
                      {
                        "ref": "lead-42",
                        "name": "Anna Svensson",
                        "organization": {
                          "domain": "nordviklogistik.se"
                        }
                      }
                    ],
                    "reveal": [
                      "email",
                      "phone"
                    ],
                    "maxCredits": 2,
                    "dryRun": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per person, in request order, with the request's credit totals.",
            "headers": {
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              },
              "Funnelfeedr-Credits-Remaining": {
                "description": "The account's credit balance after this request. Absent when the account has no credit subscription.",
                "schema": {
                  "type": "number"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchPeopleResponse"
                },
                "examples": {
                  "hit": {
                    "summary": "Hit: one contact matched; email and phone still masked",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-42",
                          "status": "matched",
                          "matchedBy": "name",
                          "contact": {
                            "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                            "name": "Anna Svensson-Berg",
                            "jobTitle": "Head of Procurement",
                            "isExecutive": false,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "XXXXX@nordviklogistik.se",
                            "emailIsMasked": true,
                            "phone": "+46701XXXXXX",
                            "phoneIsMasked": true,
                            "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  },
                  "hit-revealed": {
                    "summary": "Hit with reveal: email and phone unmasked and charged",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-42",
                          "status": "matched",
                          "matchedBy": "name",
                          "contact": {
                            "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                            "name": "Anna Svensson-Berg",
                            "jobTitle": "Head of Procurement",
                            "isExecutive": false,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "anna.svensson-berg@nordviklogistik.se",
                            "emailIsMasked": false,
                            "phone": "+46701234567",
                            "phoneIsMasked": false,
                            "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                          },
                          "reveal": {
                            "creditCost": 1.2,
                            "newInfoTypes": [
                              "email",
                              "phone"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": []
                          }
                        }
                      ],
                      "creditsCharged": 1.2,
                      "creditsQuoted": 1.2,
                      "creditsRemaining": 411.2,
                      "dryRun": false
                    }
                  },
                  "no-match": {
                    "summary": "No hit: the person is not at the organization, or the organization is unknown. Free.",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-44",
                          "status": "not_found",
                          "reason": "person_not_found",
                          "matchedBy": "name"
                        },
                        {
                          "ref": "lead-45",
                          "status": "not_found",
                          "reason": "organization_not_found",
                          "matchedBy": "name"
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  },
                  "ambiguous": {
                    "summary": "Several contacts fit: candidates, nothing revealed or charged",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-46",
                          "status": "ambiguous",
                          "matchedBy": "name",
                          "candidates": [
                            {
                              "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                              "name": "Anna Svensson-Berg",
                              "jobTitle": "Head of Procurement",
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB"
                            },
                            {
                              "id": "e3b0c442-98fc-4c14-9afb-f4c8996fb924",
                              "name": "Anna Svensson",
                              "jobTitle": "Warehouse Manager",
                              "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                              "organizationName": "Nordvik Logistik AB"
                            }
                          ]
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  },
                  "dry-run": {
                    "summary": "Dry run: what the reveal would cost; nothing spent",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-42",
                          "status": "matched",
                          "matchedBy": "name",
                          "contact": {
                            "id": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90",
                            "name": "Anna Svensson-Berg",
                            "jobTitle": "Head of Procurement",
                            "isExecutive": false,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "XXXXX@nordviklogistik.se",
                            "emailIsMasked": true,
                            "phone": "+46701XXXXXX",
                            "phoneIsMasked": true,
                            "linkedInUrl": "https://linkedin.com/in/anna-svensson-berg"
                          },
                          "reveal": {
                            "creditCost": 1.2,
                            "newInfoTypes": [
                              "email",
                              "phone"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": []
                          }
                        },
                        {
                          "ref": "lead-43",
                          "status": "matched",
                          "matchedBy": "linkedInUrl",
                          "contact": {
                            "id": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2",
                            "name": "Johan Lindqvist",
                            "jobTitle": "CFO",
                            "isExecutive": true,
                            "isDecisionMaker": true,
                            "organizationId": "6f9d2f1e-6f37-4a2f-9a3e-1b5c0f4d7e21",
                            "organizationName": "Nordvik Logistik AB",
                            "email": "XXXXX@nordviklogistik.se",
                            "emailIsMasked": true,
                            "phoneIsMasked": false
                          },
                          "reveal": {
                            "creditCost": 0.2,
                            "newInfoTypes": [
                              "email"
                            ],
                            "alreadyRevealedInfoTypes": [],
                            "unavailableInfoTypes": [
                              "phone"
                            ]
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 1.4,
                      "creditsRemaining": 412.4,
                      "dryRun": true
                    }
                  },
                  "invalid-item": {
                    "summary": "An unusable item; the rest of the batch still runs",
                    "value": {
                      "results": [
                        {
                          "ref": "lead-47",
                          "status": "invalid",
                          "error": {
                            "code": "missing_organization",
                            "message": "A name needs an organization (id, domain, orgNumber or linkedInUrl): a name alone names nobody."
                          }
                        }
                      ],
                      "creditsCharged": 0,
                      "creditsQuoted": 0,
                      "creditsRemaining": 412.4,
                      "dryRun": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — An Idempotency-Key header is required",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_required": {
                    "summary": "Spend call without Idempotency-Key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_required",
                      "title": "An Idempotency-Key header is required",
                      "status": 400,
                      "detail": "This call spends credits. Send an Idempotency-Key header (a new UUID per logical request) so a retry cannot charge twice.",
                      "code": "idempotency_key_required",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_api_key` — Invalid API key; `api_key_revoked` — API key revoked; `api_key_expired` — API key expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/invalid_api_key",
                      "title": "Invalid API key",
                      "status": 401,
                      "detail": "Send an API key as 'Authorization: Bearer ff_live_…'. App sessions and extension tokens are not accepted here.",
                      "code": "invalid_api_key",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_revoked": {
                    "summary": "Key revoked",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_revoked",
                      "title": "API key revoked",
                      "status": 401,
                      "detail": "This API key has been revoked. Create a new key in Settings → API keys.",
                      "code": "api_key_revoked",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "api_key_expired": {
                    "summary": "Key expired after a roll",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/api_key_expired",
                      "title": "API key expired",
                      "status": 401,
                      "detail": "This API key was rolled and its overlap has ended. Use the replacement key.",
                      "code": "api_key_expired",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_credits` — Not enough credits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "insufficient_credits": {
                    "summary": "Account cannot pay",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/insufficient_credits",
                      "title": "Not enough credits",
                      "status": 402,
                      "detail": "This request needs 3.4 credits; the account has 1.2. Nothing was revealed or charged. Buy credits in Funnelfeedr under Settings → Subscription, then retry.",
                      "code": "insufficient_credits",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "creditsRemaining": 1.2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`feature_not_enabled` — The External API is not enabled for this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "feature_not_enabled": {
                    "summary": "External API not enabled",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/feature_not_enabled",
                      "title": "The External API is not enabled for this account",
                      "status": 403,
                      "detail": "The External API is not enabled for this account. Request access in Settings → API keys.",
                      "code": "feature_not_enabled",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_in_use` — A request with this Idempotency-Key is still in progress",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_in_use": {
                    "summary": "Same key still running",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_in_use",
                      "title": "A request with this Idempotency-Key is still in progress",
                      "status": 409,
                      "detail": "A request with this Idempotency-Key is still running. Retry shortly to get its response.",
                      "code": "idempotency_key_in_use",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — The request body must be JSON",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "Body is not JSON",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/unsupported_media_type",
                      "title": "The request body must be JSON",
                      "status": 415,
                      "detail": "Send the request body as JSON, with 'Content-Type: application/json'.",
                      "code": "unsupported_media_type",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reused` — Idempotency-Key was already used with a different request; `validation_failed` — The request is invalid; `too_many_items` — Too many items in one request; `credit_cap_exceeded` — The request would cost more than maxCredits",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "idempotency_key_reused": {
                    "summary": "Key reused with another body",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/idempotency_key_reused",
                      "title": "Idempotency-Key was already used with a different request",
                      "status": 422,
                      "detail": "This Idempotency-Key was already used with a different request. Use a new key for a new request.",
                      "code": "idempotency_key_reused",
                      "retryable": false,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "validation_failed": {
                    "summary": "Request invalid",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/validation_failed",
                      "title": "The request is invalid",
                      "status": 422,
                      "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
                      "code": "validation_failed",
                      "retryable": false,
                      "errors": {
                        "maxCredits": [
                          "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
                        ]
                      },
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "too_many_items": {
                    "summary": "More than 25 items",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/too_many_items",
                      "title": "Too many items in one request",
                      "status": 422,
                      "detail": "This request has 40 items; the limit is 25. Split it into several requests.",
                      "code": "too_many_items",
                      "retryable": false,
                      "maxItems": 25,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  },
                  "credit_cap_exceeded": {
                    "summary": "Would cost more than maxCredits",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/credit_cap_exceeded",
                      "title": "The request would cost more than maxCredits",
                      "status": 422,
                      "detail": "This reveal costs 3.4 credits, more than maxCredits (2). Nothing was revealed or charged. Raise maxCredits or reveal fewer contacts or info types.",
                      "code": "credit_cap_exceeded",
                      "retryable": false,
                      "creditsRequired": 3.4,
                      "maxCredits": 2,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Policy": {
                "description": "The rate-limit policies this request counted against (IETF draft), for example '\"overall\";q=60;w=60, \"search\";q=20;w=60'.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Requests remaining (r) and seconds until reset (t) per policy (IETF draft), for example '\"overall\";r=59;t=42, \"search\";r=17;t=42'.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/rate_limited",
                      "title": "Rate limit exceeded",
                      "status": 429,
                      "detail": "Rate limit exceeded for 'spend' (5/min). Retry after 12 seconds.",
                      "code": "rate_limited",
                      "retryable": true,
                      "retryAfterSeconds": 12,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Internal error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalApiProblemModel"
                },
                "examples": {
                  "internal_error": {
                    "summary": "Unexpected error",
                    "value": {
                      "type": "https://funnelfeedr.com/developers/errors/internal_error",
                      "title": "Internal error",
                      "status": 500,
                      "detail": "An unexpected error occurred. Retrying may succeed; if it keeps failing, contact support with the traceId.",
                      "code": "internal_error",
                      "retryable": true,
                      "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "x-credit-cost": {
          "unit": "credits per contact",
          "spendsWhen": "when `reveal` is set and `dryRun` is false",
          "defaults": {
            "email": 0.2,
            "phone": 1,
            "linkedIn": 0
          },
          "rules": [
            "Prices are per contact and info type; email and phone together cost the sum.",
            "An info type the account has already revealed (within the last year) costs nothing again.",
            "A contact with none of the requested info types is not charged.",
            "A contact that appears more than once in a request is charged once; each of its results shows that one price, so add up creditsQuoted, not the results.",
            "An account group can have its own prices; GET /external/v1/credits shows the balance, not prices.",
            "A request is charged all-or-nothing, and never more than maxCredits."
          ]
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ExternalApiProblemModel": {
        "required": [
          "code",
          "detail",
          "title",
          "type"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "A documentation URL for the code: https://funnelfeedr.com/developers/errors/{code}."
          },
          "title": {
            "type": "string",
            "description": "Short summary, the same for every occurrence of the code."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code, repeated in the body.",
            "format": "int32"
          },
          "detail": {
            "type": "string",
            "description": "What went wrong with this request, and what to do about it."
          },
          "code": {
            "type": "string",
            "description": "Stable error code: `invalid_api_key`, `api_key_revoked`, `api_key_expired`,\n`feature_not_enabled`, `not_found`,\n`method_not_allowed`, `unsupported_media_type`, `validation_failed`, `too_many_items`, `credit_cap_exceeded`,\n`idempotency_key_required`, `idempotency_key_reused`,\n`idempotency_key_in_use`, `insufficient_credits`,\n`rate_limited` or `internal_error`."
          },
          "retryable": {
            "type": "boolean",
            "description": "True only for `rate_limited` (wait for `Retry-After`), `idempotency_key_in_use`\nand `internal_error`."
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "`validation_failed` only: messages per request field, keyed by the field's JSON path\n            (for example `maxCredits` or `items`).",
            "nullable": true
          },
          "creditsRequired": {
            "type": "number",
            "description": "`credit_cap_exceeded` and `insufficient_credits`: what the request would have\n            cost. Nothing was spent.",
            "format": "double",
            "nullable": true
          },
          "maxCredits": {
            "type": "number",
            "description": "`credit_cap_exceeded` only: the `maxCredits` the request carried.",
            "format": "double",
            "nullable": true
          },
          "creditsRemaining": {
            "type": "number",
            "description": "`insufficient_credits` only: the account's balance when the request ran.",
            "format": "double",
            "nullable": true
          },
          "retryAfterSeconds": {
            "type": "integer",
            "description": "`rate_limited` only: seconds until a retry can succeed (also in `Retry-After`).",
            "format": "int64",
            "nullable": true
          },
          "traceId": {
            "type": "string",
            "description": "Correlates the request with Funnelfeedr's logs; quote it when contacting support.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Every error the External API returns, as RFC 9457 problem details\n(`Content-Type: application/problem+json`). Branch on `code`, never on `title` or\n`detail`. When `retryable` is false, retrying the same request gives the same error —\nchange the request (or top up credits) first. Documentation only: the body is written by the\nAPI's problem writer, and this type describes its shape for the OpenAPI document."
      },
      "ExternalContactModel": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Funnelfeedr's id for the contact. Pass it to `contacts/reveal`.",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Full name; for a switchboard, the name Funnelfeedr has for the line."
          },
          "jobTitle": {
            "type": "string",
            "description": "Job title as recorded at the organization.",
            "nullable": true
          },
          "isExecutive": {
            "type": "boolean",
            "description": "True when the title places the contact in an executive role.",
            "nullable": true
          },
          "isDecisionMaker": {
            "type": "boolean",
            "description": "True when the title places the contact in a decision-making role.",
            "nullable": true
          },
          "organizationId": {
            "type": "string",
            "description": "The organization this contact was found at, when known.",
            "format": "uuid",
            "nullable": true
          },
          "organizationName": {
            "type": "string",
            "description": "Name of that organization.",
            "nullable": true
          },
          "email": {
            "type": "string",
            "description": "Email address. Masked to `XXXXX@domain` while `emailIsMasked` is true. Absent\nwhen Funnelfeedr has no email for the contact.",
            "nullable": true
          },
          "emailIsMasked": {
            "type": "boolean",
            "description": "True while the email above is masked rather than the real address."
          },
          "phone": {
            "type": "string",
            "description": "Phone number. Masked to its first digits while `phoneIsMasked` is true. Absent when\nFunnelfeedr has no phone number for the contact.",
            "nullable": true
          },
          "phoneIsMasked": {
            "type": "boolean",
            "description": "True while the phone number above is masked rather than the real number."
          },
          "linkedInUrl": {
            "type": "string",
            "description": "LinkedIn profile URL. Never masked and never charged for.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A contact Funnelfeedr knows at an organization — a person, or an organization's switchboard —\nexactly as the web app shows it to this account: email and phone are masked until the account\nreveals them (`contacts/reveal`, or `reveal` on `people/match` and\n`organizations/switchboards/match`), and stay readable for a year after. Name, title and\nLinkedIn are never masked. Trust the `…IsMasked` flags, not the shape of the value."
      },
      "ExternalCreditBalanceModel": {
        "type": "object",
        "properties": {
          "included": {
            "type": "number",
            "description": "Credits from the plan's recurring allowance; spent before purchased credits.",
            "format": "double"
          },
          "purchased": {
            "type": "number",
            "description": "Credits bought separately; they do not reset.",
            "format": "double"
          },
          "total": {
            "type": "number",
            "description": "What a reveal can spend right now.",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "The account's credit balance, as charged by reveals: included credits (the plan's allowance,\nspent first) and purchased credits."
      },
      "ExternalItemError": {
        "required": [
          "code",
          "message"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable, machine-readable reason: `missing_identifier` (the item names nothing to look\nup), `invalid_value` (a field is present but unusable — an email without '@', a\nLinkedIn URL that is not a profile, an empty name), `missing_organization` (a name was\ngiven without an organization), `missing_id` (no id on a reveal item) or\n`invalid_cursor` (a `cursor` that was altered, or that pages another organization\nthan the item's identifiers name)."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation naming the field to fix."
          }
        },
        "additionalProperties": false,
        "description": "Why one item of a batch could not be processed. Present only on results whose\n`status` is `invalid`; the rest of the batch still ran. Fix the item and send it again — retrying it unchanged gives the same answer."
      },
      "ExternalOrganizationIdentifier": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "A Funnelfeedr organization id, from an earlier response.",
            "format": "uuid",
            "nullable": true
          },
          "domain": {
            "type": "string",
            "description": "The website domain or URL: `volvo.se`, `www.volvo.se` and\n`https://www.volvo.se/about` all work. Matches only the organization flagged as the\ndomain's owner; subsidiaries sharing a parent's site do not match.",
            "nullable": true
          },
          "orgNumber": {
            "type": "string",
            "description": "National registration number, with or without the hyphen: `556012-5790` or\n`5560125790`. Pair it with `countryCode`; without one the digit count picks\nthe registry (10 digits SE, 9 NO, 8 DK or FI).",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country of `orgNumber`: SE, NO, DK or FI.",
            "nullable": true
          },
          "linkedInUrl": {
            "type": "string",
            "description": "The organization's LinkedIn page, `https://www.linkedin.com/company/volvo-group`.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Identifies one organization. Give at least one identifier; when several are given they are tried\nin the order `id`, `orgNumber`, `linkedInUrl`, `domain`, and the first one that\nmatches wins (the result's `matchedBy` says which). Organization names are not accepted: a\nname is ambiguous, and a silently wrong organization is worse than a miss."
      },
      "ExternalOrganizationMatchedBy": {
        "enum": [
          "id",
          "orgNumber",
          "linkedInUrl",
          "domain"
        ],
        "type": "string",
        "description": "The identifier an organization was matched on."
      },
      "ExternalOrganizationModel": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Funnelfeedr's id for the organization. Stable; store it to look the organization up again\n(`organizations/match` with `id`).",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "The registered name."
          },
          "detailsRevealed": {
            "type": "boolean",
            "description": "True when this object carries every detail Funnelfeedr has for the organization: the account\nhas revealed them (within the last year), or there are none beyond the free basics. False\nwhen details are held back; reveal them with `organizations/match` and\n`reveal: [\"details\"]`."
          },
          "organizationNumber": {
            "type": "string",
            "description": "A detail, unless you matched by `orgNumber`. The national registration number as the\nregistry files it (Swedish numbers with a hyphen, for example `556012-5790`). A Swedish\nsole trader's number is a personal identity number and comes back with its last four digits\nmasked (`850101-XXXX`).",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country of registration: SE, NO, DK or FI.",
            "nullable": true
          },
          "domain": {
            "type": "string",
            "description": "A detail, unless you matched by `domain` (then it is the domain you sent, normalized).\nThe website domain, without `www.`.",
            "nullable": true
          },
          "website": {
            "type": "string",
            "description": "A detail. The website URL.",
            "nullable": true
          },
          "linkedInUrl": {
            "type": "string",
            "description": "A detail, unless you matched by `linkedInUrl` (then it is the page you sent,\nnormalized). The organization's LinkedIn page.",
            "nullable": true
          },
          "city": {
            "type": "string",
            "description": "City of the registered address.",
            "nullable": true
          },
          "addressLine1": {
            "type": "string",
            "description": "A detail. Street address of the registered address.",
            "nullable": true
          },
          "addressLine2": {
            "type": "string",
            "description": "A detail. Second address line, when there is one.",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "description": "A detail. Postal code of the registered address.",
            "nullable": true
          },
          "stateOrProvince": {
            "type": "string",
            "description": "A detail. State, province or region of the registered address, when the registry has one.",
            "nullable": true
          },
          "employeesMin": {
            "type": "integer",
            "description": "A detail. Lower bound of the registered employee range.",
            "format": "int32",
            "nullable": true
          },
          "employeesMax": {
            "type": "integer",
            "description": "A detail. Upper bound of the registered employee range; absent when open-ended or unknown.",
            "format": "int32",
            "nullable": true
          },
          "revenueMin": {
            "type": "number",
            "description": "A detail. Lower bound of annual revenue, in `revenueCurrency`.",
            "format": "double",
            "nullable": true
          },
          "revenueMax": {
            "type": "number",
            "description": "A detail. Upper bound of annual revenue, in `revenueCurrency`.",
            "format": "double",
            "nullable": true
          },
          "revenueCurrency": {
            "type": "string",
            "description": "A detail. ISO 4217 currency of the revenue figures, for example SEK.",
            "nullable": true
          },
          "oneSentenceDescription": {
            "type": "string",
            "description": "A detail. What the organization does, in one English sentence.",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "A detail. A longer English description of the organization.",
            "nullable": true
          },
          "technologies": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "A detail. Technologies detected on the organization's website, by name.",
            "nullable": true
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "A detail. Keywords describing the organization's business.",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "A detail. Funnelfeedr's industry, sector and business-model tags, by display name.",
            "nullable": true
          },
          "legalForm": {
            "type": "string",
            "description": "Registered legal form, for example `Aktiebolag`.",
            "nullable": true
          },
          "foundedYear": {
            "type": "integer",
            "description": "Year the organization was founded.",
            "format": "int32",
            "nullable": true
          },
          "url": {
            "type": "string",
            "description": "The organization's page in the Funnelfeedr web app.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "An organization from Funnelfeedr's Nordic registry data. The basics are free on every response:\n`id`, `url`, `name`, `countryCode`, `legalForm`, `city`,\n`foundedYear`, and the identifier you matched it by (an `orgNumber` match returns\n`organizationNumber`, a `domain` match `domain`, a `linkedInUrl` match\n`linkedInUrl`). Everything else is a detail: absent until the account reveals the\norganization's details (`organizations/match` with `reveal: [\"details\"]`), then\nreturned on every response for a year. `detailsRevealed` says which."
      },
      "ExternalOrganizationRevealType": {
        "enum": [
          "details"
        ],
        "type": "string",
        "description": "What `organizations/match` can reveal."
      },
      "ExternalPersonCandidate": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "jobTitle": {
            "type": "string",
            "nullable": true
          },
          "organizationId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "organizationName": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "One of several contacts an `ambiguous` match could mean. Contact information is not included;\npick by title or organization, then reveal by id."
      },
      "ExternalRevealInfoType": {
        "enum": [
          "email",
          "phone",
          "linkedIn"
        ],
        "type": "string",
        "description": "A kind of contact information a reveal unmasks. Default per-contact prices (an account group may\nhave its own): `email` 0.2 credits, `phone` 1.0 credits. `linkedIn` is never masked\nand always free; it is accepted so a caller can ask for \"everything\" without special-casing it."
      },
      "ExternalRevealLine": {
        "type": "object",
        "properties": {
          "creditCost": {
            "type": "number",
            "description": "Credits this contact adds to the request's total. 0 when everything requested is already\nrevealed, unavailable, or free.",
            "format": "double"
          },
          "newInfoTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalRevealInfoType"
            },
            "description": "Info types this reveal unmasks (or would unmask, on a dry run)."
          },
          "alreadyRevealedInfoTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalRevealInfoType"
            },
            "description": "Requested info types the account had already revealed. Not charged."
          },
          "unavailableInfoTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalRevealInfoType"
            },
            "description": "Requested info types Funnelfeedr has no value for on this contact. Not charged — a contact\nwith none of the requested types is skipped entirely."
          }
        },
        "additionalProperties": false,
        "description": "What a reveal did — or, with `dryRun`, would do — for one contact. Only info types the\naccount has not revealed yet are charged; the rest cost nothing again."
      },
      "ExternalSwitchboardRevealType": {
        "enum": [
          "email",
          "phone"
        ],
        "type": "string",
        "description": "What `organizations/switchboards/match` can reveal, at the non-person prices."
      },
      "MatchOrganizationContactsItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "A Funnelfeedr organization id, from an earlier response.",
            "format": "uuid",
            "nullable": true
          },
          "domain": {
            "type": "string",
            "description": "The website domain or URL: `volvo.se`, `www.volvo.se` and\n`https://www.volvo.se/about` all work. Matches only the organization flagged as the\ndomain's owner; subsidiaries sharing a parent's site do not match.",
            "nullable": true
          },
          "orgNumber": {
            "type": "string",
            "description": "National registration number, with or without the hyphen: `556012-5790` or\n`5560125790`. Pair it with `countryCode`; without one the digit count picks\nthe registry (10 digits SE, 9 NO, 8 DK or FI).",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country of `orgNumber`: SE, NO, DK or FI.",
            "nullable": true
          },
          "linkedInUrl": {
            "type": "string",
            "description": "The organization's LinkedIn page, `https://www.linkedin.com/company/volvo-group`.",
            "nullable": true
          },
          "ref": {
            "type": "string",
            "description": "Your own reference for the item — a CRM id, a row number. Echoed back unchanged on its\nresult; Funnelfeedr never reads it. Optional, at most 200 characters.",
            "nullable": true
          },
          "cursor": {
            "type": "string",
            "description": "The `nextCursor` of an earlier result, exactly as returned, to read that organization's\nnext page. The cursor already names the organization, so the identifiers can be left out; if\nthey are sent and name another organization, the item is `invalid`\n(`invalid_cursor`).",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "One organization: its identifiers and a `ref`, as in `organizations/match` — or, for\nits next page of contacts, the `cursor` an earlier result returned, which needs no\nidentifier beside it."
      },
      "MatchOrganizationContactsNotFoundReason": {
        "enum": [
          "organization_not_found"
        ],
        "type": "string"
      },
      "MatchOrganizationContactsRequest": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationContactsItem"
            },
            "description": "1–25 organizations, identified exactly as in `organizations/match`, or by the\n`cursor` of an earlier result. An empty list or more than 25 rejects the whole request\n(422).",
            "nullable": true
          },
          "limit": {
            "type": "integer",
            "description": "Contacts per organization, 1–50. Defaults to 20. An item with a `cursor` keeps the page\nsize of the request that started it, whatever this says.",
            "format": "int32",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Up to 25 organizations whose target-role contacts to read, each answered separately and in\norder. Free. An item with a `cursor` reads the next page of the organization that cursor\ncame from."
      },
      "MatchOrganizationContactsResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationContactsResult"
            }
          }
        },
        "additionalProperties": false,
        "description": "One result per requested organization, in request order."
      },
      "MatchOrganizationContactsResult": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "The item's `ref`, echoed unchanged (absent when none was sent).",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/MatchOrganizationContactsStatus"
          },
          "reason": {
            "$ref": "#/components/schemas/MatchOrganizationContactsNotFoundReason"
          },
          "matchedBy": {
            "$ref": "#/components/schemas/ExternalOrganizationMatchedBy"
          },
          "organization": {
            "$ref": "#/components/schemas/ExternalOrganizationModel"
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalContactModel"
            },
            "description": "One page of the organization's contacts that match the account's target contact roles,\nranked the way the Funnelfeedr web app ranks them. Email and phone are masked until\nrevealed. Absent unless `matched`.",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "description": "How many contacts the organization has in total across pages, under the same filter. Absent\nunless `matched`.",
            "format": "int32",
            "nullable": true
          },
          "filteredByTargetRoles": {
            "type": "boolean",
            "description": "True when the contacts are only those matching the account's target contact roles — by who\nthey are, whatever contact info a role asks for, as the web app's badge shows them. False\nwhen the account has no target contact roles set up: then every contact at the organization\nis returned, as the web app's contacts tab lists them. Absent unless `matched`.",
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "description": "Send as an item's `cursor` for the organization's next page; null on its last page.\nPresent on every `matched` result, absent otherwise.",
            "nullable": true
          },
          "error": {
            "$ref": "#/components/schemas/ExternalItemError"
          }
        },
        "additionalProperties": false,
        "description": "The answer for one organization: a page of its target-role contacts."
      },
      "MatchOrganizationContactsStatus": {
        "enum": [
          "matched",
          "not_found",
          "invalid"
        ],
        "type": "string"
      },
      "MatchOrganizationItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "A Funnelfeedr organization id, from an earlier response.",
            "format": "uuid",
            "nullable": true
          },
          "domain": {
            "type": "string",
            "description": "The website domain or URL: `volvo.se`, `www.volvo.se` and\n`https://www.volvo.se/about` all work. Matches only the organization flagged as the\ndomain's owner; subsidiaries sharing a parent's site do not match.",
            "nullable": true
          },
          "orgNumber": {
            "type": "string",
            "description": "National registration number, with or without the hyphen: `556012-5790` or\n`5560125790`. Pair it with `countryCode`; without one the digit count picks\nthe registry (10 digits SE, 9 NO, 8 DK or FI).",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country of `orgNumber`: SE, NO, DK or FI.",
            "nullable": true
          },
          "linkedInUrl": {
            "type": "string",
            "description": "The organization's LinkedIn page, `https://www.linkedin.com/company/volvo-group`.",
            "nullable": true
          },
          "ref": {
            "type": "string",
            "description": "Your own reference for the item — a CRM id, a row number. Echoed back unchanged on its\nresult; Funnelfeedr never reads it. Optional, at most 200 characters.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "One organization lookup: the organization identifiers, plus a `ref`."
      },
      "MatchOrganizationResult": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "The item's `ref`, echoed unchanged (absent when none was sent).",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/MatchOrganizationStatus"
          },
          "matchedBy": {
            "$ref": "#/components/schemas/ExternalOrganizationMatchedBy"
          },
          "organization": {
            "$ref": "#/components/schemas/ExternalOrganizationModel"
          },
          "creditCost": {
            "type": "number",
            "description": "What revealing this organization's details costs (on a dry run) or cost (otherwise): 0 when\nthe account has already revealed them or there are none to reveal. Absent unless\n`matched` and `reveal` was requested.",
            "format": "double",
            "nullable": true
          },
          "error": {
            "$ref": "#/components/schemas/ExternalItemError"
          }
        },
        "additionalProperties": false,
        "description": "The answer for one lookup."
      },
      "MatchOrganizationStatus": {
        "enum": [
          "matched",
          "not_found",
          "invalid"
        ],
        "type": "string"
      },
      "MatchOrganizationSwitchboardNotFoundReason": {
        "enum": [
          "organization_not_found",
          "no_switchboard"
        ],
        "type": "string"
      },
      "MatchOrganizationSwitchboardResult": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "The item's `ref`, echoed unchanged (absent when none was sent).",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/MatchOrganizationSwitchboardStatus"
          },
          "reason": {
            "$ref": "#/components/schemas/MatchOrganizationSwitchboardNotFoundReason"
          },
          "matchedBy": {
            "$ref": "#/components/schemas/ExternalOrganizationMatchedBy"
          },
          "organization": {
            "$ref": "#/components/schemas/ExternalOrganizationModel"
          },
          "contact": {
            "$ref": "#/components/schemas/ExternalContactModel"
          },
          "reveal": {
            "$ref": "#/components/schemas/ExternalRevealLine"
          },
          "error": {
            "$ref": "#/components/schemas/ExternalItemError"
          }
        },
        "additionalProperties": false,
        "description": "The answer for one organization."
      },
      "MatchOrganizationSwitchboardStatus": {
        "enum": [
          "matched",
          "not_found",
          "invalid"
        ],
        "type": "string"
      },
      "MatchOrganizationSwitchboardsRequest": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationItem"
            },
            "description": "1–25 organizations, identified exactly as in `organizations/match`. An empty list or\nmore than 25 rejects the whole request (422).",
            "nullable": true
          },
          "reveal": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalSwitchboardRevealType"
            },
            "description": "What to reveal on every `matched` switchboard: `phone`, `email` or both. Omit\nto only find them (free).",
            "nullable": true
          },
          "maxCredits": {
            "type": "number",
            "description": "The most this request may spend. Required when `reveal` is set and `dryRun` is\nfalse. If the reveal would cost more, the request fails with 422 `credit_cap_exceeded`\nand nothing is spent.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "Find and price the reveal without spending anything: each result's `reveal` shows what\nit would cost, `creditsQuoted` the total, and `creditsCharged` is 0."
          }
        },
        "additionalProperties": false,
        "description": "Up to 25 organizations whose switchboard to find, each answered separately and in order.\nFinding is free. Set `reveal` to also unmask the switchboards' phone numbers and/or emails in\nthe same call, which spends credits: it then needs an `Idempotency-Key` header and\n`maxCredits` — unless `dryRun` is true."
      },
      "MatchOrganizationSwitchboardsResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationSwitchboardResult"
            }
          },
          "creditsCharged": {
            "type": "number",
            "description": "Credits this request spent. 0 without `reveal`, on a dry run, or when nothing new was unmasked.",
            "format": "double"
          },
          "creditsQuoted": {
            "type": "number",
            "description": "What the reveal costs (on a dry run) or cost (otherwise). Can exceed `creditsCharged`\nonly on a dry run.",
            "format": "double"
          },
          "creditsRemaining": {
            "type": "number",
            "description": "The account's balance after this request, also sent as the `Funnelfeedr-Credits-Remaining`\nheader. Null when the account has no credit subscription.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "True when the request was a dry run and nothing was spent."
          }
        },
        "additionalProperties": false,
        "description": "One result per requested organization, in request order, plus the request's credit totals."
      },
      "MatchOrganizationsRequest": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationItem"
            },
            "description": "1–25 lookups. An empty list or more than 25 rejects the whole request (422).",
            "nullable": true
          },
          "reveal": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalOrganizationRevealType"
            },
            "description": "`[\"details\"]` to reveal the details of every `matched` organization. Omit to only\n            match (free). `not_found` and `invalid` items are never charged.",
            "nullable": true
          },
          "maxCredits": {
            "type": "number",
            "description": "The most this request may spend. Required when `reveal` is set and `dryRun` is\nfalse. If the reveal would cost more, the request fails with 422 `credit_cap_exceeded`\nand nothing is spent.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "Match and price the reveal without spending anything: each result's `creditCost` shows\nwhat it would cost, `creditsQuoted` the total, and `creditsCharged` is 0."
          }
        },
        "additionalProperties": false,
        "description": "Up to 25 organizations to look up, each answered separately and in order. Matching is free and\nreturns the basics. Set `reveal` to `[\"details\"]` to also reveal the matched\norganizations' details in the same call, which spends credits: it then needs an\n`Idempotency-Key` header and `maxCredits` — unless `dryRun` is true."
      },
      "MatchOrganizationsResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchOrganizationResult"
            }
          },
          "creditsCharged": {
            "type": "number",
            "description": "Credits this request spent. 0 without `reveal`, on a dry run, or when nothing new was revealed.",
            "format": "double"
          },
          "creditsQuoted": {
            "type": "number",
            "description": "What the reveal costs (on a dry run) or cost (otherwise). Can exceed `creditsCharged`\nonly on a dry run.",
            "format": "double"
          },
          "creditsRemaining": {
            "type": "number",
            "description": "The account's balance after this request, also sent as the `Funnelfeedr-Credits-Remaining`\nheader. Null when the account has no credit subscription.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "True when the request was a dry run and nothing was spent."
          }
        },
        "additionalProperties": false,
        "description": "One result per requested item, in request order, plus the request's credit totals."
      },
      "MatchPeopleRequest": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchPersonItem"
            },
            "description": "1–25 people. An empty list or more than 25 rejects the whole request (422).",
            "nullable": true
          },
          "reveal": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalRevealInfoType"
            },
            "description": "Info types to reveal for every `matched` person: any of `email`, `phone`,\n`linkedIn`. Omit to only match (free). `ambiguous` and `not_found` items are\nnever revealed or charged.",
            "nullable": true
          },
          "maxCredits": {
            "type": "number",
            "description": "The most this request may spend. Required when `reveal` is set and\n`dryRun` is false. If the reveal would cost more, the request fails with 422\n`credit_cap_exceeded` and nothing is spent.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "Match and price the reveal without spending anything: each result's `reveal` shows what\nit would cost, `creditsQuoted` the total, and `creditsCharged` is 0."
          }
        },
        "additionalProperties": false,
        "description": "Up to 25 people to find, each answered separately and in order. Matching is free. Set\n`reveal` to also unmask the matched people's contact information in the same call,\nwhich spends credits: it then needs an `Idempotency-Key` header and `maxCredits` —\nunless `dryRun` is true."
      },
      "MatchPeopleResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchPersonResult"
            }
          },
          "creditsCharged": {
            "type": "number",
            "description": "Credits this request spent. 0 without `reveal`, on a dry run, or when nothing new was unmasked.",
            "format": "double"
          },
          "creditsQuoted": {
            "type": "number",
            "description": "What the reveal costs (on a dry run) or cost (otherwise). Can exceed `creditsCharged`\nonly on a dry run.",
            "format": "double"
          },
          "creditsRemaining": {
            "type": "number",
            "description": "The account's balance after this request, also sent as the `Funnelfeedr-Credits-Remaining`\nheader. Null when the account has no credit subscription.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "True when the request was a dry run and nothing was spent."
          }
        },
        "additionalProperties": false,
        "description": "One result per requested person, in request order, plus the request's credit totals."
      },
      "MatchPersonItem": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Your own reference, echoed back unchanged on the result. Optional, at most 200 characters.",
            "nullable": true
          },
          "linkedInUrl": {
            "type": "string",
            "description": "The person's LinkedIn profile, `https://www.linkedin.com/in/anna-svensson`.",
            "nullable": true
          },
          "email": {
            "type": "string",
            "description": "A work email address. Matched exactly, ignoring case, and only against addresses the account\nalready sees unmasked (revealed before, or belonging to the account); any other address is\n`person_not_found`.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Full name, for example `Anna Svensson`. Matched after normalising case, accents\n(`Åsa Öberg` = `Asa Oberg`) and hyphenated surnames (`Anna Svensson` matches\n`Anna Svensson-Berg`). Not fuzzy: nicknames (Kalle for Karl) and typos do not match.\nNeeds `organization`.",
            "nullable": true
          },
          "organization": {
            "$ref": "#/components/schemas/ExternalOrganizationIdentifier"
          }
        },
        "additionalProperties": false,
        "description": "One person to find, by exactly one of: `linkedInUrl`, `email`, or `name` plus an\n`organization`. When more than one is given, the strongest is used: LinkedIn, then email,\nthen name + organization."
      },
      "MatchPersonMatchedBy": {
        "enum": [
          "linkedInUrl",
          "email",
          "name"
        ],
        "type": "string"
      },
      "MatchPersonNotFoundReason": {
        "enum": [
          "organization_not_found",
          "person_not_found"
        ],
        "type": "string"
      },
      "MatchPersonResult": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "The item's `ref`, echoed unchanged.",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/MatchPersonStatus"
          },
          "reason": {
            "$ref": "#/components/schemas/MatchPersonNotFoundReason"
          },
          "matchedBy": {
            "$ref": "#/components/schemas/MatchPersonMatchedBy"
          },
          "contact": {
            "$ref": "#/components/schemas/ExternalContactModel"
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalPersonCandidate"
            },
            "description": "Up to 5 contacts that fit, for `ambiguous`; absent otherwise.",
            "nullable": true
          },
          "reveal": {
            "$ref": "#/components/schemas/ExternalRevealLine"
          },
          "error": {
            "$ref": "#/components/schemas/ExternalItemError"
          }
        },
        "additionalProperties": false,
        "description": "The answer for one person."
      },
      "MatchPersonStatus": {
        "enum": [
          "matched",
          "ambiguous",
          "not_found",
          "invalid"
        ],
        "type": "string"
      },
      "RevealContactItem": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Your own reference, echoed back unchanged. Optional, at most 200 characters.",
            "nullable": true
          },
          "contactId": {
            "type": "string",
            "description": "The Funnelfeedr contact id.",
            "format": "uuid",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "One contact to reveal."
      },
      "RevealContactResult": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "The item's `ref`, echoed unchanged.",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/RevealContactStatus"
          },
          "contactId": {
            "type": "string",
            "description": "The contact id from the request.",
            "format": "uuid",
            "nullable": true
          },
          "reveal": {
            "$ref": "#/components/schemas/ExternalRevealLine"
          },
          "contact": {
            "$ref": "#/components/schemas/ExternalContactModel"
          },
          "error": {
            "$ref": "#/components/schemas/ExternalItemError"
          }
        },
        "additionalProperties": false,
        "description": "The answer for one contact."
      },
      "RevealContactStatus": {
        "enum": [
          "revealed",
          "quoted",
          "not_found",
          "invalid"
        ],
        "type": "string"
      },
      "RevealContactsRequest": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RevealContactItem"
            },
            "description": "1–25 contacts, by the ids other endpoints return.",
            "nullable": true
          },
          "infoTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalRevealInfoType"
            },
            "description": "What to unmask: any of `email`, `phone`, `linkedIn`. Required.",
            "nullable": true
          },
          "maxCredits": {
            "type": "number",
            "description": "The most this request may spend; required unless `dryRun` is true. If the reveal\nwould cost more, the request fails with 422 `credit_cap_exceeded` and nothing is spent.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "Price the reveal without spending: results show each contact's cost, `creditsQuoted` the\ntotal, `creditsCharged` is 0 and the contacts stay masked. The price a dry run quotes is\nthe price a real run charges, unless someone reveals some of them in between (then it is lower)."
          }
        },
        "additionalProperties": false,
        "description": "Unmask contact information for up to 25 known contacts. Spends credits, all-or-nothing: either\nevery contact in the request is revealed and charged, or nothing is."
      },
      "RevealContactsResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RevealContactResult"
            }
          },
          "creditsCharged": {
            "type": "number",
            "description": "Credits this request spent; 0 on a dry run.",
            "format": "double"
          },
          "creditsQuoted": {
            "type": "number",
            "description": "What the reveal costs (dry run) or cost.",
            "format": "double"
          },
          "creditsRemaining": {
            "type": "number",
            "description": "The account's balance after this request, also sent as the `Funnelfeedr-Credits-Remaining`\nheader. Null when the account has no credit subscription.",
            "format": "double",
            "nullable": true
          },
          "dryRun": {
            "type": "boolean",
            "description": "True when the request was a dry run and nothing was spent."
          }
        },
        "additionalProperties": false,
        "description": "One result per requested contact, in request order, plus the request's credit totals."
      }
    },
    "responses": {
      "not_found": {
        "description": "`not_found` — Not found. Any request to a path under `/external/v1` that is not one of the endpoints below answers with this.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ExternalApiProblemModel"
            },
            "examples": {
              "not_found": {
                "summary": "No such path",
                "value": {
                  "type": "https://funnelfeedr.com/developers/errors/not_found",
                  "title": "Not found",
                  "status": 404,
                  "detail": "No endpoint at POST /external/v1/organizations/lookup. Check the path against https://funnelfeedr.com/developers/api-reference.",
                  "code": "not_found",
                  "retryable": false,
                  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                }
              }
            }
          }
        }
      },
      "method_not_allowed": {
        "description": "`method_not_allowed` — Method not allowed. Any path answers with this when it is called with an HTTP method it does not take, for example GET on a POST-only path.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ExternalApiProblemModel"
            },
            "examples": {
              "method_not_allowed": {
                "summary": "Wrong HTTP method",
                "value": {
                  "type": "https://funnelfeedr.com/developers/errors/method_not_allowed",
                  "title": "Method not allowed",
                  "status": 405,
                  "detail": "GET is not allowed on /external/v1/organizations/match. Use POST.",
                  "code": "method_not_allowed",
                  "retryable": false,
                  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
                }
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "description": "An account API key from Settings → API keys.",
        "scheme": "bearer",
        "bearerFormat": "ff_live_…"
      }
    }
  },
  "tags": [
    {
      "name": "Contacts",
      "description": "Reveal (unmask) contact information. This is where credits are spent."
    },
    {
      "name": "Credits",
      "description": "The account's credit balance."
    },
    {
      "name": "Organizations",
      "description": "Organizations in Funnelfeedr's Nordic registry data, their target-role contacts and their\nswitchboards. Free, except revealing an organization's details or a switchboard's phone number."
    },
    {
      "name": "People",
      "description": "Find specific people, and optionally reveal their contact information in the same call."
    }
  ]
}
