Skip to Content
IntegrationsKorea Import API

Korea Import API

The Korea Import API serves your Korean cars, at your prices, as JSON for your own website. You get a key, you call one URL, and you render the cars however your site already looks — no iframe, no framework, no Scriptiflow branding.

This API is part of the Korea Import add-on. The cars it returns are the ones you publish from Korea Import, priced by your pricing rules and any per-car overrides.

This page is written for whoever builds your website. Everything below works with plain HTML and JavaScript.

Getting a key

Open the Public API panel

Go to Settings → Integrations → Public API.

📷 Screenshot to captureSettings → Integrations → Public API, showing existing keys and the 'Create a key' button

Create a key

Click Create a key and give it a name you’ll recognise later — Website or Dealer site is enough.

Copy the key now

The full key (sf_pk_live_…) is shown once, right after you create it. Copy it into your site’s configuration before closing the dialog. Afterwards the panel only shows the first few characters, so you can tell keys apart.

Turn the feed on

The feed only answers while Public API is switched on for the brand. If it’s off, every request comes back as 503.

If you lose a key, you can’t recover it — create a new one, swap it into your site, then revoke the old one. Revoking takes effect immediately: any site still using that key starts getting 401.

A key belongs to one brand and returns that brand’s cars only. If you run several rooftops, create one key per brand.

Authentication

Send the key as a header on every request:

GET /api/public/v1/vehicles HTTP/1.1 Host: api.scriptiflow.com X-Scriptiflow-Key: sf_pk_live_a1b2c3d4e5f6

For embeds where you genuinely can’t set a header, the key can also go in the query string:

https://api.scriptiflow.com/api/public/v1/vehicles?key=sf_pk_live_a1b2c3d4e5f6

A key in a URL ends up in server logs, browser history, analytics and Referer headers. Use the header wherever you can, and treat a ?key= key as something you’ll rotate more often.

Base URL

https://api.scriptiflow.com/api/public/v1

All responses are JSON, UTF-8, and all prices are integers in the brand’s currency (EUR unless you changed it).

Endpoints

GET /vehicles

The list. Returns published cars with paging.

GET /api/public/v1/vehicles?make=BMW&year_from=2018&sort=price_asc&page=1&limit=24
ParameterValuesDefaultNotes
scopeselected, allyour brand’s settingselected = only the cars you picked. all = everything your brand publishes. Your brand’s setting is the ceiling — asking for all never returns more than you’ve chosen to publish.
makeexact make, e.g. BMWUse a value from /filters.
modelexact model, e.g. 520dUse a value from /filters.
year_fromyear, e.g. 2018Inclusive.
year_toyear, e.g. 2022Inclusive.
fuele.g. DieselUse a value from /filters.
gearboxe.g. AutomaticUse a value from /filters.
bodye.g. SUVUse a value from /filters.
price_fromwhole units, e.g. 15000Matched against the price in the response, not any internal figure.
price_towhole units, e.g. 40000Same.
km_tokilometres, e.g. 120000Maximum mileage.
sortnew, price_asc, price_desc, km, yearnewnew = most recently added first. km = lowest mileage first. year = newest model year first.
page1 and up11-based.
limit1–10024Cars per page.

Unknown parameters are ignored, so you can add your own cache-busting values without breaking anything.

GET /vehicles/:id

One car, same fields as a list row.

GET /api/public/v1/vehicles/b0c1d2e3-4f56-7890-abcd-ef0123456789

Returns 404 if the car isn’t published by your brand any more — a sold car disappears from the feed, so handle 404 on your detail pages and redirect back to the list.

GET /filters

The values that actually exist in your feed right now, with counts. Use it to build your filter dropdowns instead of hard-coding makes.

{ "makes": [{ "value": "Hyundai", "count": 214 }, { "value": "BMW", "count": 96 }], "models": [{ "value": "520d", "count": 12 }], "fuels": [{ "value": "Diesel", "count": 180 }, { "value": "Petrol", "count": 142 }], "bodies": [{ "value": "SUV", "count": 160 }, { "value": "Sedan", "count": 122 }], "years": { "min": 2016, "max": 2024 } }

models is empty until you pass ?make= — models are returned for one make at a time, because the full list is thousands of entries.

Example response

{ "data": [ { "id": "b0c1d2e3-4f56-7890-abcd-ef0123456789", "make": "BMW", "model": "520d", "generation": "G30", "year": 2019, "mileage_km": 78400, "fuel": "Diesel", "gearbox": "Automatic", "body_type": "Sedan", "color": "Black", "power_ps": 190, "price": 24950, "currency": "EUR", "price_display": "24.950 €", "price_note": "excl. customs", "images": [ "https://cdn.scriptiflow.com/korea/b0c1d2e3/01.jpg", "https://cdn.scriptiflow.com/korea/b0c1d2e3/02.jpg" ], "image": "https://cdn.scriptiflow.com/korea/b0c1d2e3/01.jpg", "source": "encar" } ], "meta": { "page": 1, "limit": 24, "total": 312, "has_more": true } }

A few fields are worth knowing:

FieldWhat it is
idA stable, non-guessable ID. Use it in your own URLs and pass it to /vehicles/:id. It stays the same for the life of the car.
priceA number, for sorting and filtering on your side. null means price on request.
price_displayThe same price already formatted for the brand’s market. Render this one — it saves you writing currency formatting.
price_noteOptional line the dealer sets, e.g. excl. customs. May be null; show it next to the price when present.
imageThe first image, so you don’t have to index into images. images is the full ordered list.
sourceWhere the car came from. Currently always encar.

Fields that don’t apply to a car are null rather than missing, so you can read them without guarding every access.

Copy-paste: fetch

const SF_KEY = 'sf_pk_live_a1b2c3d4e5f6'; const SF_API = 'https://api.scriptiflow.com/api/public/v1'; async function loadVehicles(params = {}) { const qs = new URLSearchParams({ limit: '24', ...params }); const res = await fetch(`${SF_API}/vehicles?${qs}`, { headers: { 'X-Scriptiflow-Key': SF_KEY } }); if (res.status === 429) { const retryAfter = Number(res.headers.get('Retry-After') || 5); throw new Error(`Rate limited. Retry in ${retryAfter}s.`); } if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error || `Request failed with status ${res.status}`); } const { data, meta } = await res.json(); return { vehicles: data, meta }; } async function main() { try { const { vehicles, meta } = await loadVehicles({ make: 'BMW', year_from: '2018', sort: 'price_asc' }); console.log(`Showing ${vehicles.length} of ${meta.total} cars`); } catch (err) { console.error('Could not load vehicles:', err.message); } } main();

Copy-paste: plain HTML embed

Paste this where you want the cars to appear, replace the key, and you’re done. No build step, no dependencies, works in any CMS block that allows HTML.

<div id="sf-cars"></div> <script> (function () { var API = 'https://api.scriptiflow.com/api/public/v1/vehicles'; var KEY = 'sf_pk_live_a1b2c3d4e5f6'; // <- your key var LIMIT = 24; var mount = document.getElementById('sf-cars'); if (!mount) return; var style = document.createElement('style'); style.textContent = [ '#sf-cars{display:grid;gap:16px;font-family:system-ui,-apple-system,sans-serif;', 'grid-template-columns:repeat(auto-fill,minmax(240px,1fr))}', '#sf-cars .sf-card{border:1px solid #e5e5e5;border-radius:8px;overflow:hidden;background:#fff}', '#sf-cars .sf-card img{display:block;width:100%;height:170px;object-fit:cover;background:#f2f2f2}', '#sf-cars .sf-body{padding:12px}', '#sf-cars .sf-title{margin:0 0 4px;font-size:15px;font-weight:600}', '#sf-cars .sf-meta{margin:0 0 8px;font-size:13px;color:#666}', '#sf-cars .sf-price{font-size:16px;font-weight:600}', '#sf-cars .sf-note{font-size:12px;font-weight:400;color:#666}', '#sf-cars .sf-msg{grid-column:1/-1;font-size:14px;color:#666}' ].join(''); document.head.appendChild(style); var ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }; function esc(value) { return String(value == null ? '' : value).replace(/[&<>"']/g, function (c) { return ESCAPES[c]; }); } function km(value) { return typeof value === 'number' ? value.toLocaleString('de-DE') + ' km' : ''; } function card(car) { var title = [car.make, car.model].filter(Boolean).join(' '); var meta = [car.year, km(car.mileage_km), car.fuel, car.gearbox].filter(Boolean).join(' · '); var note = car.price_note ? ' <span class="sf-note">' + esc(car.price_note) + '</span>' : ''; var image = car.image ? '<img src="' + esc(car.image) + '" alt="' + esc(title) + '" loading="lazy">' : ''; return '<article class="sf-card">' + image + '<div class="sf-body">' + '<p class="sf-title">' + esc(title) + '</p>' + '<p class="sf-meta">' + esc(meta) + '</p>' + '<div class="sf-price">' + esc(car.price_display || 'Price on request') + note + '</div>' + '</div>' + '</article>'; } mount.innerHTML = '<p class="sf-msg">Loading cars…</p>'; fetch(API + '?limit=' + LIMIT, { headers: { 'X-Scriptiflow-Key': KEY } }) .then(function (res) { if (!res.ok) throw new Error('HTTP ' + res.status); return res.json(); }) .then(function (body) { var cars = (body && body.data) || []; mount.innerHTML = cars.length ? cars.map(card).join('') : '<p class="sf-msg">No cars available right now.</p>'; }) .catch(function (err) { mount.innerHTML = '<p class="sf-msg">Cars are unavailable right now.</p>'; console.error('[scriptiflow]', err); }); })(); </script>

To link each card to your own detail page, build the URL from car.id inside card():

var href = '/cars/' + encodeURIComponent(car.id); return '<a class="sf-card" href="' + href + '">' + image + '…</a>';

A browser request that sends the X-Scriptiflow-Key header triggers a CORS preflight. If you’ve set a domain allowlist on the key, add the exact origin your site is served from (including https:// and any www.), otherwise the browser blocks the call.

Errors

Errors come back as JSON — { "error": "…" } — with a matching HTTP status.

StatusMeaningWhat to do
401The key is missing, wrong, or has been revoked.Check the header name and the key value. If the key was revoked in Settings → Integrations → Public API, create a new one and swap it in.
403The request’s origin isn’t on the key’s domain allowlist.Add the exact origin (scheme + host, e.g. https://www.example.com) to the key, or clear the allowlist. Server-side calls aren’t affected.
429Too many requests for this key.Back off and retry after the number of seconds in the Retry-After response header. Add caching — see below.
503The Public API is switched off for the brand, or the subscription has lapsed.Nothing to fix in code. Check Settings → Integrations → Public API is on and the Korea Import add-on is active. Show your visitors a fallback and retry later.

Treat everything except 200 as “show the page without the feed” rather than letting an error break the page.

Domain allowlist

Each key can carry a list of allowed origins. When the list is set, the API only returns CORS headers for those origins — a browser on any other site refuses the response. When the list is empty, browsers on any site are allowed.

Be clear about what that buys you: the allowlist stops other websites from using your key in a browser. It is not a secret-keeper. Anyone who reads your page source can copy the key and call the API from a server or from curl, where CORS doesn’t apply.

So:

  • Use the allowlist to stop casual reuse of your key on other sites.
  • Rotate the key if you think it’s been taken. It’s a two-minute job.
  • Don’t put anything behind this key that you wouldn’t publish on your own website — which is exactly why the response contains no cost data.

Rate limits

The default is 120 requests per minute per key. That’s generous for a website and tight for a scraper.

When you exceed it you get 429 with a Retry-After header in seconds. Respect it — retrying immediately just extends the block.

The practical advice:

  • Cache on your side. The feed changes at most every few minutes, so a 5- to 10-minute cache is invisible to visitors and removes almost all your traffic.
  • Fetch once per page, not once per card. Pull a page of cars and render from that array.
  • Don’t proxy every visitor. If your site is server-rendered, fetch the feed on a schedule into your own cache rather than on each page view.

What is not in the response, and why

The feed carries the retail car — what a customer sees. It never includes:

  • the Korean purchase price, or anything in won
  • your landed cost, duty, freight or fees
  • the pricing rule that produced the price
  • any link, ID or reference back to the Korean source listing
  • the Korean seller’s name, type or city

That’s deliberate, and it’s on your side. Your website is public, so anything in this feed is public: a competitor with curl would otherwise be able to read your buying price, work out your margin, and find your supply chain. What they get instead is your shop window — the same thing your customers get.

If you need cost figures for internal use, they stay in Scriptiflow under Korea Import, behind your team’s login.

Troubleshooting

Everything returns 401. The header is X-Scriptiflow-Key and the value is the full key including the sf_pk_live_ prefix — not the shortened version shown in the panel. If you only have the shortened one, the full key is gone; create a new key.

It works from my terminal but not from the browser. That’s CORS. Add your site’s exact origin to the key’s domain allowlist, or clear the allowlist. A trailing slash, a missing www., or http:// instead of https:// all count as a different origin.

Cars disappeared from my site. A car leaves the feed when it’s sold or when you unpublish it in Korea Import. Check the car is still in your selection and that the brand’s Public API setting still covers it.

Everything returns 503. The Public API switch is off, or the Korea Import add-on isn’t active on the brand. See Settings → Integrations → Public API, or contact support@scriptiflow.com.

Last updated on