Features Measurements Memory Download Guide Developers Support Log in Get started
DEVELOPERS

Developer docs

This page is for people building something on top of FastFind. If you just want to use it, see Guide is where it is.

Get started

What can you build with FastFind?

FastFind a small search server inside your own PC can be brought up. That lets other programs fetch the search results.

As of 0.81, web access is off by default. You have to turn it on in Settings → Web access before the REST API below opens. There is also a way to connect without turning it on — Named pipe has the details.

  • Fetching file lists from PowerShell or Python scripts
  • Adding search to your own launcher or tool
  • Adding your own action to the right-click menu on results (plugins)

The index stays inside your PC and never leaves it. The server answers only on 127.0.0.1.

Where does the server run?

It is 9090 by default. You can change it in Settings.

It only opens once you turn web access on, though. When it is off, that port does not exist even while the program is running — because no listening socket is created at all.

Can I find duplicate files through the API?

Yes. Just put it in q — it is the same syntax you type in the search box.

GET /api/search?q=dupe:내용 ext:jpg
GET /api/search?q=dupe: path:D:\사진
GET /api/search?q=empty:

Results come back grouped together. The order of the groups is always the same, so sending the same query twice will not shuffle them.

dupe:content actually reads the files, so it takes longer than other queries. If there are more than 20,000 candidates it stops short of comparing contents and returns a note in error — because you must not delete on the assumption that the results are complete.

http://127.0.0.1:9090

If the app is not running, neither is the server. It is safer to check with /api/status first.

Here is something actually built this way
MyStart Built with FastFind

Find files on your PC straight from the browser start page. Web search and local files in one place.

BrowserStart page
types a query
MyStart agent/v1/localfiles
relays reads only
FastFind127.0.0.1:9090
/api/search

The browser does not call FastFind directly. If the API were exposed to a web page, any site could scrape your file list — so a trusted agent sits in the middle and passes reads only.
Before calling, it checks /api/status to see whether the app is up; if not it returns 503 fastfind_not_running and hands off to the install guide. The user only has to install and run FastFind — no sign-in, no configuration.

Visit mystart.youngsam.net ↗

Authentication

A token is required

Nobody should be able to scrape your file list, so nothing is returned without a token.

Send it in one of two ways.

Authorization: Bearer <token>Send it in a header
ff_local_token=<token>Send it as a cookie (when calling from a browser)

You get the token from the /login screen. Only these two addresses open without a token.

POST /api/local-auth/login

REST API

Search — GET /api/search
curl -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:9090/api/search?q=견적서%20ext:pdf&max=50"

What to send

qThe query. It uses the search syntax from the Guide exactly (ext: size: path:, initial consonants …)
maxHow many at most (omit for the default)
name_onlytrue searches names only; false searches the whole path
caseCase sensitive
wordWhole words only
regexRegular expression
sort ascSort field and direction
rawSearches exactly what you typed. Turns off the Korean-word guessing — see below

For true/false values, 1 0 true false on off yes no are all accepted. A bare &raw is also treated as on.

ext: how to use it

ext:jpg,pngSeveral, comma-separated — use this form
ext:jpg|pngDoes not work. jpg|png is read as a single extension name, giving 0 results
ext:jpg ext:pngIf you write it twice, only the last one is kept

⚠ When calling from a program, add raw=1 .

By default a few words are understood the way a person would mean them. That is good for people, but when a program calls it, the query changes without warning, so the results look wrong.

However, if you send a single word, no kind is guessed. q=photo finds files with photo in the name. The table below applies when there are two or more words(e.g. photo 찾아줘).

photo image 이미지 사진 그림→ all image extensions (the word itself disappears)
doc document 문서→ all document extensions
audio music 음악 오디오→ all audio extensions
video 동영상 영화 영상→ all video extensions
엑셀 xls,xlsx,csv
작은 대용량→ size condition
10개 상위5→ result limit
By size 최신순 가나다순→ sorting
A folder 파일만→ filter by kind
GET /api/search?q=photo             → 이름에 photo 가 든 파일 (낱말 하나 — 짐작 안 함)
GET /api/search?q=photo 찾아줘      → 이미지 파일 전부 (낱말 둘 — 짐작함)
GET /api/search?q=photo 찾아줘&raw=1 → 이름에 photo 가 든 파일

ext: path: size: and similar syntax raw=1 still works even with it on. That is not guessing — it is exactly what you wrote. Only the guessing is turned off.

What we understood comes back in the response.

"interpreted": {
  "keyword": null,                 // 실제로 찾은 낱말 (짐작으로 사라지면 null)
  "extensions": ["jpg","png", …],  // 걸린 확장자
  "guessed": true,                 // ← 참이면 짐작이 낱말을 삼킨 것입니다
  "guessed_words": ["photo"],      // 무엇 때문에 바뀌었는지
  "raw": false
}

guessed trueraw=1 call again with Yes.

What comes back

{
  "results": [
    {
      "name": "견적서_한빛건설.pdf",
      "path": "C:\\작업\\2026\\견적서_한빛건설.pdf",
      "size": 284915,
      "ext": "pdf",
      "modified": 1786012800,
      "is_dir": false
    }
  ],
  "total": 12,
  "time_ms": 5.3,
  "query": "견적서 ext:pdf"
}

total is the total number matching the conditions, and results is however many of those were returned. modified is seconds since 1970.

Status — GET /api/status

Tells you whether the index is ready and how many items it holds. Check this before sending a search and you can tell apart the cases where the app is closed or still indexing.

{
  "indexed_files": 4663246,
  "version": "0.86.0",
  "status": "ready"
}

The response also carries fields like engine, engine_gen and mem, but those are values we use when tracking down problems and change without notice. Do not rely on them.

Suggestions — GET /api/suggest

Gets the candidates to show while typing. It returns a single array of strings.

GET /api/suggest?q=견적

["견적서_한빛건설.pdf", "견적서_양식.hwp", "견적_2026.xlsx"]
A short PowerShell example
$t = "여기에 토큰"
$r = Invoke-RestMethod -Uri "http://127.0.0.1:9090/api/search?q=ext:log" `
     -Headers @{ Authorization = "Bearer $t" }
$r.results | Select-Object name, path, size | Format-Table
A short Python example
import requests, urllib.parse

TOKEN = "여기에 토큰"
q = urllib.parse.quote("견적서 ext:pdf")
r = requests.get(f"http://127.0.0.1:9090/api/search?q={q}&max=20",
                 headers={"Authorization": f"Bearer {TOKEN}"})
for f in r.json()["results"]:
    print(f["size"], f["path"])
Other endpoints — they exist, but are not promised

The following are open too. But they were made for our own web UI and change without notice. If you use them, do so knowing they may break.

GET /api/recentRecently created or modified files
GET /api/open?path=…Opens that file
GET /api/open-folder?path=…Opens the folder that holds the file
GET /api/preview?path=…Preview image (returned as an image)
GET /api/analysisWhat is taking up space
GET /api/browse?path=…Listing inside a folder
/api/favorites /add /removeFavourites
/api/smart-folders /add /remove /searchSaved searches
/api/history /clearSearch history

If you use any of these often, let us know. We will move the ones people actually use into the promised list first.

When it fails
Connection refusedWeb access is off (the default). Or the program is not running, or the port is different
401The token is missing or wrong
404No such endpoint

The program goes away together when the user quits it, and If you do not turn on web access, it does not even come up in the first place. Please do not assume it is always running. Before calling it, /api/status to check first, and have it move on quietly if that fails — that is the safer way.

If you would rather not ask your users to “turn it on in Settings”, Named pipe — use that. It is always open, whether or not web access is on.

How do I find the port?

The default is 9090, but the user can change it in Settings. If you are shipping a tool, let the port be asked for or configured.

You can also read it from the settings file.

%LOCALAPPDATA%\FastFind\settings.json
Where is the version number?

/api/status . /api/version · /api/health · /api/info is None(404).

To check whether a feature is available, look at /api/statusversion .

Some places never show up in results

By default there are folders that are not indexed. They are temporary and system folders — indexing them only clutters results and bloats the index.

System$Recycle.Bin · System Volume Information · Recovery
WindowsWindows\WinSxS · Installer · servicing · Temp · Prefetch · SoftwareDistribution
Install leftovers$WINDOWS.~BT · $WINDOWS.~WS · ProgramData\Microsoft
UserAppData\Local\Temp · .cache · node_modules · .git · __pycache__

These extensions tmp · temp · bak · old · lnk · url are not indexed either.

The user can change this list in Settings → Index scope , so it can differ from machine to machine.

Connecting over the pipe

How to connect without turning on web access

As of 0.81, web access is off by default. So to use the REST API you would have to ask the user to turn that setting on, and that is not a good thing to ask.

Instead of Named pipeis there. Independently of web access, is always open, and, and does not open a port.

\\.\pipe\FastFind

on top of the pipe and similar REST routerssits on top of it unchanged. So the paths, the parameters, and the responses are all exactly as described above. Only the channel changes — write an HTTP request into the pipe and read the response, and that is all.

How to call it

On Windows, the pipe is just like a file is all you open. No special library is needed.

GET /api/search?q=견적서 HTTP/1.1
Host: 127.0.0.1
Connection: close

Host is must be the numeric address. If you write a name it is rejected (this is to prevent DNS rebinding). For a request that changes a value, Sec-Fetch-Site: same-origin as well. It is not needed for read-only requests.

Connection: close — do not leave it out. Without it the connection stays open, and the side trying to read to the end waits forever. It shows up not as “it does not work” but as “it stalls”, which makes it hard to find.

Two errors worth retrying

The pipe allows one connection per instance. Several are opened in advance, but if requests pile up they may run short for a moment.

231 ERROR_PIPE_BUSYis full. A moment later, try
2 ERROR_FILE_NOT_FOUNDis the instant it is handed over. this also needs to be retried

2 is commonly read as “not there” and people give up at that point, but here it means “please wait a moment”is often the cause. In both cases, please retry briefly a few times (five times at 10 ms each is plenty). If it still 2 keeps coming up, then it really is a case of “FastFind is not there”.

Who can connect

only the user currently signed in to this PC can connect. Not other accounts on the same PC, and not anything across the network.

There is no ID or password to enter — Windows tells us who is connecting, so when we create the pipe we simply write “this user only” once.

Plugins

How do plugins work?

It inserts your own action into the menu that appears when you right-click a search result.

A plugin is a separate executable. We do not load DLLs into the app — if someone else's code goes wrong, FastFind must not die with it. That is why you can write one in any language.

FastFind → 플러그인 :  myplugin.exe --path "C:\\file.txt"
플러그인 → FastFind :  표준출력으로 JSON 한 덩어리
What it can return
messageShows a message box
copyCopies to the clipboard
openOpens that path
{"action":"message","title":"줄 수","text":"1,284줄"}
{"action":"copy","text":"복사할 내용"}
{"action":"open","path":"C:\\어딘가"}
The shortest plugin — five lines of Python

It counts the lines in the file you picked and tells you.

import sys, json
path = sys.argv[sys.argv.index("--path") + 1]
n = sum(1 for _ in open(path, encoding="utf-8", errors="ignore"))
print(json.dumps({"action": "message", "title": "줄 수",
                  "text": f"{n:,}줄"}, ensure_ascii=False))

Build this into an exe, put it in the folder below, and place plugin.json next to it.

%LOCALAPPDATA%\\FastFind\\plugins\\linecount\\
    run.exe
    plugin.json
{
  "id": "linecount",
  "name": "줄 수 세기",
  "description": "고른 파일의 줄 수를 셉니다",
  "version": "1.0.0",
  "author": "내 이름"
}

Restart FastFind and Count lines appears in the right-click menu on results.

What already ships with it

Four are installed together. Use them as a reference when writing your own.

  • File info — size, time, attributes
  • Hash — computes SHA-256
  • Image info — dimensions and format
  • Text stats — lines, words and characters

What we promise, and what we do not

How much do you promise will not change?

We will be straight about it. We promise only what is written on this page.

/api/searchPromised. We will not remove fields or change their meaning
/api/statusOnly the three fields indexed_files, version and status are promised
/api/suggestPromised
The plugin contractPromised
Other endpointsInternal. Changes without notice

There are more endpoints inside the app, but most are used by our own web UI. Publishing them would turn them into promises we cannot easily change, so we have listed only the stable ones here.

New fields may be added. That does not break what exists, so write your code to ignore fields it does not know.

What is missing for now
  • There is no official library. You have to call it over HTTP yourself
  • There are no change notifications (webhooks). Nothing tells you when a file appears, so you have to poll if you need to know
  • A plugin cannot change the result list. For now it goes as far as doing one thing from the right-click menu

If you need something, let us know. If someone will use it, we will build it.

The command line works too

You can call the executable directly instead of using the server.

FastFind.exe --console ext:log > list.txt

The details are in the command-line section of the Guide.