The complete list. If a request is not in this table, the app does not make it — and since 2.5 most of what the app does makes no request at all.
| Host | Call | When |
|---|---|---|
| opensolr.com | GET /app/authorize (browser) | Sign-in |
| opensolr.com | POST /app/token | Sign-in: code and verifier for email, API key and plan limits |
| opensolr.com | get_index_list | Setting the app up, and at the start of a sync run: is the phone's index in the account? |
| opensolr.com | vector_regions | Creating the index: where vector search runs |
| opensolr.com | create_index | Creating the index |
| opensolr.com | upload_zip_config_files | A new index, or one without the app's schema |
| opensolr.com | get_core_info | Address and credentials of the index |
| opensolr.com | get_account_summary | Plan limits and usage: at the start of a sync run, again when it ends, and on the account screen |
| api.opensolr.com | photos_ingest | The photos a sync found new or changed, up to ten per call: the whole indexing of a photo happens there, the printed text included |
| api.opensolr.com | photos_words | After you save tags, names or wording: up to 50 photos per call, no pictures attached |
| api.opensolr.com | photos_select | Once per typed search, on plans with vector search |
| the phone's index | POST /select | The one read that seeds the phone's copy at install or reinstall, searching (with spellcheck), the facets behind a typed search, the filter lists, the duplicate groups, the photos with a position for the map, and checking the schema |
| the phone's index | POST /suggest | Autocomplete, as you type |
| the phone's index | GET /opensolr-photos-config | The configuration version the index runs, at app start and at every sync |
| tile.openstreetmap.org | GET map tiles | Only while the map screen is open; cached on the phone |
| api.github.com | GET /repos/phpcip/opensolr-photos/releases/latest | Whether a newer release exists: once a day, and whenever you tap Check for updates. No credentials, nothing about you or your photos |
| the phone's index | POST /update | Deleting the photos that left the phone or that you deleted, saving an edit, emptying the index for a reset, committing |
The account API calls live under /solr_manager/api/ on their host. Credentials always travel in the request body, never in a URL. The app follows no redirects on these calls, so a credential is never re-sent to a host the app did not name.
What no longer goes out. The phone keeps its own copy of every document its index holds, so a sync compares your folders with that copy instead of walking the index: for a library of 10,000 photos the walk was about 21 requests and 1.8 MB, on every single sync, and it is gone. Plain browsing — no words typed, no filters — makes no request either: the years, the months, the days, their counts and the photos in them all come from the phone. So are the tag and name suggestions and the list of what the selected photos already carry. The filter lists are asked for once and kept until something is written.
What still goes out, as the table says: a typed search and its facets, autocomplete, the duplicate groups, the map's photos, the writes, and the checks at the top of a sync run. Held answers from an earlier read are reused for as long as you chose in the account screen.
Once, at install or reinstall, the app reads your index into its own copy: POST /select with cursor paging, 10,000 documents a page, so a library of 10,000 photos costs one request and is never read again. Every field is read except the search vector and the duplicate keys — the id, the path, the file name, the size, the dates, the camera and its EXIF, the place, the words the photo was read into, the printed text, the people, your own tags and the file's md5. From then on every write keeps the copy in step: photos_ingest and photos_words both answer with the document they wrote, and that answer is what the phone stores. A read that does not reach the end does not count, and is done again.
A POST with a JSON body: email, api_key, index_name and photos, up to ten objects, each with the photo's id, path, folder, file name, size and dimensions, the md5 of the original file, the base64 of its 1024 px JPEG copy carrying the original's EXIF, and, when this phone holds them, your tags, your meaning and the persons named on the photo. The server checks every upload by its content, reads the EXIF with exiftool, asks the image model what the photo shows, has the text printed in it read when it carries any, asks the embedder for the vector of those words, turns the GPS position into a place through nearby_places, keeps the tags, the wording and the names already in the index for photos the phone did not speak for, writes the duplicate keys from the model's words and the EXIF, builds the complete document and writes it into your index. It answers, per photo:
On a plan without vector search, or with the month's AI allowance used up, nothing is sent to be read: the copy still travels, because the EXIF is read out of it on the server, but it is never shown to the image model, never read for printed text and never embedded. The photo is indexed by its date, its camera, its place, its file name and your own words, and search is lexical.
A tenth of an AI request per photo that needed a model (ten photos per request), only then; a photo sent again is served from cache. Words are never written without their vector: a photo the allowance cannot cover goes in with everything else and is read again at a later sync. A photo already read keeps what was read out of it — its words and its printed text — through a later pass that cannot read it again, as long as the file's md5 says it is the same file; a file that really changed starts again. The date is kept the same way: a photo that carries no date of its own takes the one already in the index, so writing your tags into the file no longer moves it to today. The phone's part ends with the upload; nothing comes back to be written from the phone. Full reference: image_index for the words-and-vector step on its own.
Tags, the people in a photo and your own wording, for photos already in the index, with no picture attached, because only the words changed. A POST with email, api_key, index_name and photos, up to 50 objects, each with the photo's id and whichever of tags, persons, meaning and file_hash changed. Saving is done on the phone first and is finished there; the sync that starts straight after carries the change up. The server reads each document whole, puts the new words in over what was there, keeps everything else the document holds, makes the batch's search vectors again from those words in one embedding call on plans with vector search, and writes the documents back with one POST. It answers, per photo, the document it has just written — every field of it except the search vector and the duplicate keys — and that answer is what the phone stores in its copy:
A photo the index does not have yet answers NOT_INDEXED, and the phone sends it the ordinary way, picture and all. No model looks at a picture here: a photo whose search vector was made again costs a tenth of an AI request, the same as when it was first read, and a photo whose words are answered from cache costs nothing.
The app asks for nothing here: photos_ingest reads the text printed in every photo it receives, with tesseract on the CPUs of api.opensolr.com, every photo of the batch at the same time and while the image model works on the same batch — never on your phone. Whether a photo carries text is decided by what was read: at least four confidently read words of three letters or more, otherwise the photo is kept as having none. The reading goes into the ocr_t field of your index and the search reads it along with everything else (search).
It adds nothing to what a photo costs: the photo is a tenth of an AI request whether the work was the words, the vector or the printed text. A picture already read is answered from Opensolr's cache and costs nothing at all. The same reading is open to anyone on their own index through image_ocr, metered half a request per picture actually read.
A typed search by meaning. A POST with a JSON body: email, api_key, index_name, vector_text (what you typed, 2 to 300 characters), top_k and params, the select the app built, as name and value pairs. The server turns vector_text into a query vector, puts it in as vectorQuery, runs the select on your own photos index and answers Solr's answer as it is; the vector itself never comes back. Parameters that would send the request anywhere else than your index (shards, qt, stream.*, joins across indexes) are refused.
A tenth of an AI request per search whose vector had to be made; the same words searched again are answered from cache and cost nothing. On a plan without vector search it answers VECTOR_NOT_ALLOWED, and with the month's allowance used up ERROR_AI_MONTHLY_QUOTA_EXCEEDED; in both cases, and whenever no vector can be made, the app runs the same search on the words alone, straight on your index. The vector of a photo whose tags or words you edited is not asked for from the phone at all: photos_words makes it on the server, out of the words it has just written.
| Answer | Meaning | What the app does |
|---|---|---|
ERROR_AUTHENTICATION_FAILED | The API key was refused | Asks you to sign in again |
429 ERROR_AI_MONTHLY_QUOTA_EXCEEDED | AI requests used up | Pauses sync, words-only search; browsing and the phone's copy are unaffected |
429 with Retry-After | Rate limit | Waits and retries |
VECTOR_NOT_ALLOWED | No vector search on the plan | Continues without vectors |
NOT_OWNER_ERROR | The index is not in the account | Treated as a missing index |
| 401 from the index | Its password changed | Reads the new one and retries |
| 403 from the index | Over disk space or bandwidth | Stops, notification that opens the Opensolr account |