Remote Configuration
GhostPour serves JSON config files to your iOS app via GET /v1/config/{name}. This lets you update prompts, model lists, and capabilities without App Store releases.
How It Works
- JSON files live in
config/remote/{slug}.json, each with a top-level"version"integer - On startup, baseline configs are seeded into a persistent directory on the Docker volume. Dashboard edits are preserved across restarts and redeploys.
- All configs are loaded into memory at startup
- Your iOS app calls
GET /v1/config/{slug}on every launch - If the client sends
X-Config-Versionmatching the server version, returns{"changed": false} - Otherwise returns the full JSON payload with the new version header
GhostPour returns 200 with {"changed": false} instead of 304 Not Modified because Nginx Proxy Manager mangles bare 304 responses (no cached body to serve) into 404s for downstream clients.
Config Persistence
Configs live in two places:
| Location | Purpose |
|---|---|
config/remote/ | Baked-in baseline, shipped with the Docker image |
data/remote-config/ | Persistent runtime copy on the mounted Docker volume |
On startup, any baseline configs that don't exist in the persistent directory are copied over. This means:
- New configs added via git appear on the next deploy automatically
- Dashboard edits are preserved across container restarts
- Dashboard edits take precedence over baked-in versions for the same file
Managing Configs
Via admin dashboard: Edit in the Configs tab and click Save. Changes take effect immediately and persist across restarts. No deploy needed.
Via code: Edit the JSON in config/remote/, bump the version integer, and redeploy. If the file has been edited via the dashboard, the baked-in version won't overwrite it.
Updating a Config
{
"version": 5,
"prompts": {
"welcome": "How can I help you today?",
"summary": "Summarize the key points from this conversation."
}
}
Bump the version, redeploy (or edit via dashboard), and your iOS app picks up changes on its next launch.
Adding a New Config
Drop a .json file with a "version" field into config/remote/ and redeploy. The slug is the filename without .json. No code changes needed.
You can also create configs directly from the admin dashboard without a deploy.
Localization
The config endpoint supports localized variants via the standard Accept-Language header. Your iOS app sends this automatically based on the device locale, so no client-side changes are needed to pick up new languages.
How It Works
- The server parses the
Accept-Languageheader to extract the primary language code (e.g.,es-MX,es;q=0.9,en;q=0.8resolves toes) - It looks for
{slug}.{lang}.jsonin the config directory - If the locale variant exists, it's returned with
X-Config-LocaleandX-Config-Resolvedresponse headers - If not, the base config (English) is returned as a fallback
Adding a Language
Create a locale variant file following the naming convention {slug}.{lang}.json:
config/remote/my-prompts.json # Base (English)
config/remote/my-prompts.es.json # Spanish
config/remote/my-prompts.fr.json # French
config/remote/my-prompts.de.json # German
Each locale variant has its own independent version number, so you can update translations on their own schedule.
You can also use the admin dashboard's language picker to create new locale variants. It copies the base config as a starting template and hot-reloads immediately.
Supported Languages
Any two-letter language code works. The system is extensible by simply adding new files. No code changes or app updates required.
Multi-App Deployments
If you're running a single GhostPour instance for multiple iOS apps, prefix config slugs with the app name to avoid collisions:
config/remote/myapp-prompts.json
config/remote/myapp-providers.json
config/remote/otherapp-prompts.json
This is a naming convention, not enforced by code. Single-app deployments can use unprefixed slugs.