Play Video
make logo

Oppora in Make

Call Oppora from any Make scenario using the built-in HTTP module. Bearer auth goes in a single header, JSON bodies map directly to upstream module fields.

Before you start

  • 1An Oppora API key (Integrations → Oppora API in the app). Starts with opp_live_…
  • 2A paid Oppora plan (Pro or Max). API keys aren't available on Free.
  • 3A Make account — the HTTP app is included on every plan, including Free.

Set up the HTTP module once, copy it

Make's HTTP module doesn't have reusable named connections the way n8n or Zapier do — every call carries its own header. The cleanest pattern is to build one module with the header configured, then right-click → Clone it into other scenarios so you don't re-type the bearer.

  1. In your scenario, click + → search HTTP → pick Make a request.
  2. Configure the request:
    HTTP module — base settings
    Method:         POST
    Headers:
      Name:  Authorization
      Value: Bearer YOUR_KEY
    Body type:      Raw
    Content type:   JSON (application/json)
    Parse response: Yes
  3. Set the URL and body per recipe below.
  4. For repeat use across scenarios: right-click the module → Clone, or save the scenario as a template.

Tip: keep your key in Data Stores if you want a single source of truth — reference it as {{getValue("oppora_creds"; "api_key")}} in the header field. Useful when you rotate the key.

Recipes

Each recipe is one HTTP Make a request module. Reference fields from upstream modules with {{ 1.field_name }} (where 1. is the module number).

POST /email/search

Find email by name + domain

Resolves a verified work email when you have a person's name and their company's website domain.

Request body
{
  "first_name": "Alice",
  "last_name": "Chen",
  "domain": "stripe.com"
}
Response
{
  "email": "[email protected]",
  "status": "valid",
  "source": "oppora",
  "credit_charged": 1,
  "credits_remaining": 4823
}
  • `domain` is required — free-text company names are not accepted (they cause wrong-company matches).
  • Credit is charged only when an email is returned with status `valid` or `risky`. Misses are free.
Wire it in Make

URL: https://api.oppora.ai/api/v1/public/email/search. Paste the JSON body and replace literal values with field mappings from your trigger:

Raw body — JSON
{
  "first_name": "{{ 1.first_name }}",
  "last_name":  "{{ 1.last_name }}",
  "domain":     "{{ 1.domain }}"
}

With Parse response: Yes, downstream modules can map email and status from the response object directly.

POST /email/verify

Verify an email

Check deliverability before sending. Use `mode: "advanced"` for catch-all domains where `standard` returns `risky` or `unknown`.

Request body
{
  "email": "[email protected]",
  "mode": "standard"
}
Response
{
  "email": "[email protected]",
  "mode": "standard",
  "status": "valid",
  "credit_charged": 1,
  "credits_remaining": 4822
}
  • `mode` is `standard` (fast) or `advanced` (deep SMTP, catch-all aware). Both charge on result.
Wire it in Make

Chain after email-search. Add a Router with two filters: status = valid → send, status = risky → second verify module with "mode": "advanced".

Raw body — JSON
{
  "email": "{{ 1.email }}",
  "mode":  "standard"
}
POST /phone/search

Lookup phone from LinkedIn URL

Best with a LinkedIn URL. Falls back to (name + company) or (name + domain). DB-cache hits are free; external provider hits charge 1 phone credit on success.

Request body
{
  "linkedin_url": "https://www.linkedin.com/in/alice-chen-vp"
}
Response
{
  "phone": "+14155551234",
  "status": "valid",
  "source": "oppora_db",
  "credit_charged": 0,
  "credits_remaining": 1200
}
  • `source: "oppora_db"` = pulled from cache (free). External provider sources (wiza, dropcontact, etc.) charge 1 credit per resolved phone.
Wire it in Make

Build a single module that prefers LinkedIn URL when present. Use Make's if() function in the body:

Raw body — conditional
{
  "linkedin_url": "{{ if(length(1.linkedin_url) > 0; 1.linkedin_url; emptystring) }}",
  "first_name":   "{{ if(length(1.linkedin_url) > 0; emptystring; 1.first_name) }}",
  "last_name":    "{{ if(length(1.linkedin_url) > 0; emptystring; 1.last_name) }}",
  "domain":       "{{ if(length(1.linkedin_url) > 0; emptystring; 1.domain) }}"
}

Cleaner: split into two modules + a router on linkedin_url presence.

POST /discover/companies

Discover companies matching ICP filters

Search the global company DB by industry, size, location, revenue, funding, growth. Returns up to 100 rows per page with cursor pagination. Every row carries an Oppora `id` so you can chain into add-to-list flows.

Request body
{
  "industry": ["Software Development"],
  "hq_country": "USA",
  "size": ["51-200", "201-500"],
  "revenue_range_min": 5000000,
  "limit": 25
}
Response
{
  "data": [
    {
      "id": 8421,
      "name": "Stripe",
      "domain": "stripe.com",
      "industry": "Financial Services",
      "size": "5001-10000",
      "employee_count": 8200,
      "year_founded": 2010,
      "total_funding_usd": 8700000000,
      "country": "USA"
    }
  ],
  "count": 25,
  "total": 1842,
  "next_cursor": "eyJjdXJzb3IiOiIuLi4ifQ",
  "has_more": true,
  "credit_charged": 1,
  "credits_remaining": 4821
}
  • Resolve exact filter values via `GET /filters/industries` and `GET /filters/countries` first — wrong strings match zero rows.
  • `hq_country` is a single ISO 3-alpha code ("USA", "GBR", "IND").
Wire it in Make

For pagination, wrap the HTTP module in a Repeater + Aggregator pair, or use the Iterator on data to fan-out rows. Feed next_cursor back via a variable.

Raw body — page 1
{
  "industry": ["Software Development"],
  "hq_country": "USA",
  "size": ["51-200", "201-500"],
  "limit": 100
}
Raw body — subsequent pages
{
  "next_cursor": "{{ 2.next_cursor }}",
  "limit": 100
}
POST /discover/people

Discover people by title, department, company

Search the global people DB by title, department, management level, company, industry, skills, location, years of experience. Returns the same row shape your AI/list tools expect.

Request body
{
  "title": ["VP Sales", "Head of Sales"],
  "departments": ["Sales"],
  "management_levels": ["VP", "Director"],
  "company_name": ["Stripe", "Plaid"],
  "years_experience": ["6 to 10 years", "More than 10 years"],
  "limit": 25
}
Response
{
  "data": [
    {
      "id": 42818,
      "first_name": "Alice",
      "last_name": "Chen",
      "full_name": "Alice Chen",
      "title": "VP of Sales",
      "department": "Sales",
      "management_level": "VP",
      "linkedin_url": "https://www.linkedin.com/in/alice-chen-vp",
      "location": "San Francisco",
      "years_experience": "More than 10 years",
      "company": {
        "id": 8421,
        "name": "Stripe",
        "domain": "stripe.com"
      }
    }
  ],
  "count": 25,
  "total": 412,
  "next_cursor": "eyJjdXJzb3IiOiIuLi4ifQ",
  "has_more": true,
  "credit_charged": 1,
  "credits_remaining": 4820
}
  • Resolve exact `departments` and `management_levels` values via `GET /filters/departments` and `GET /filters/management-levels` first.
  • `years_experience` must use the exact bucket labels: "Less than 1 year", "1 to 2 years", "3 to 5 years", "6 to 10 years", "More than 10 years".
  • No email or phone in the response — chain into `/email/search` or `/phone/search` for that.
Wire it in Make

Common chain: Iterator over the company rows from discover_companies, then this module per-company. Add an Iterator on data after the call to fan-out each person into its own bundle.

Raw body — per-company decision-makers
{
  "title": ["VP Sales", "Head of Sales"],
  "departments": ["Sales"],
  "management_levels": ["VP", "Director"],
  "company_name": ["{{ 1.name }}"],
  "limit": 10
}

Need more endpoints?

The recipes above are the most common flows — Oppora exposes 17 REST endpoints in total (discovery, bulk async jobs, filter helpers, account info). See the full REST API reference for the complete surface.