All integrations

CleanJobData + Supabase

Load CleanJobData job listings into Supabase for powerful search and real-time subscriptions.

Get Free API Key

Benefits

Setup Guide

1Create a jobs table

Create a PostgreSQL table matching CleanJobData's schema.

CREATE TABLE jobs (
  id BIGINT PRIMARY KEY, title TEXT NOT NULL,
  company TEXT, location TEXT, salary_min NUMERIC,
  description TEXT, posted_at TIMESTAMPTZ
);

2Write a sync function

Create a function that fetches from CleanJobData and upserts into Supabase.

3Add full-text search

Set up PostgreSQL full-text search on your synced job data.

ALTER TABLE jobs ADD COLUMN search_vector TSVECTOR
  GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || description)) STORED;
CREATE INDEX jobs_search_idx ON jobs USING GIN(search_vector);

Advanced Recipes

Real-world patterns beyond the basic setup.

Complete sync Edge Function (supabase/functions/sync-jobs/index.ts)

A full, deployable Edge Function that fetches recently-ingested jobs and upserts them by id. Schedule it with pg_cron so it runs without an external worker.

import { createClient } from "jsr:@supabase/supabase-js@2";

Deno.serve(async (req) => {
  const apiKey = Deno.env.get("CLEANJOBDATA_API_KEY")!;
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const res = await fetch(
    "https://api.cleanjobdata.com/jobs?created_max_age=1h&limit=100",
    { headers: { "X-API-Key": apiKey } }
  );
  if (!res.ok) return new Response(`CleanJobData API ${res.status}`, { status: 502 });

  const { data: jobs } = await res.json();

  const rows = jobs.map((job: any) => ({
    id: job.id,
    title: job.title,
    company: job.company?.name ?? null,
    location: job.location,
    salary_min: job.salary_min,
    posted_at: job.published,
  }));

  const { error } = await supabase.from("jobs").upsert(rows, { onConflict: "id" });
  if (error) return new Response(error.message, { status: 500 });

  return new Response(JSON.stringify({ synced: rows.length }), {
    headers: { "Content-Type": "application/json" },
  });
});

Schedule it with pg_cron

Run inside the Supabase SQL editor, once, to invoke the Edge Function every hour.

select cron.schedule(
  'sync-cleanjobdata-jobs',
  '0 * * * *', -- every hour
  $$
  select net.http_post(
    url := 'https://<project-ref>.supabase.co/functions/v1/sync-jobs',
    headers := jsonb_build_object('Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'))
  );
  $$
);

Frequently Asked Questions

Can I use Supabase Edge Functions?

Yes — it's a natural place to run the sync: fetch from CleanJobData with `fetch()`, transform the response into your table's shape, and upsert into Postgres with the service role key. Trigger it on a `pg_cron` schedule (Supabase's built-in cron extension) so it runs without an external scheduler.

How do I keep data synced?

Run a scheduled job — an Edge Function on `pg_cron`, or any cron worker — that calls `/jobs` with `created_max_age` set to your run interval (`created_max_age=1h` for an hourly job) and upserts the results by `id`. `created_max_age` filters on when CleanJobData ingested the listing, not when the employer posted it, so it reliably catches jobs regardless of publish-date backfills. Don't reuse a pagination cursor across separate scheduled runs — a cursor only walks forward through one result set and has no meaning against a fresh query.

Should the CleanJobData key live in the Edge Function or get exposed to Supabase client libraries?

Keep it in the Edge Function's environment only (`supabase secrets set CLEANJOBDATA_API_KEY=...`), read via `Deno.env.get`. Never pass it to `supabase-js` calls made from a browser client — the sync should be the only thing that talks to CleanJobData; your frontend reads the already-synced `jobs` table instead, protected by your own Row Level Security policies.

How do I enable real-time updates on synced jobs?

Enable the Realtime replication toggle on the `jobs` table (Database → Replication in the dashboard, or `alter publication supabase_realtime add table jobs;`), then subscribe from the client with `supabase.channel('jobs-changes').on('postgres_changes', { event: '*', schema: 'public', table: 'jobs' }, callback).subscribe()`. New rows inserted by the sync Edge Function push to subscribed clients immediately, without polling.