OpenForex documentation API
Developer reference for reading, integrating, and maintaining the Society's standards publication. Updated 2026-09-24.
TVX calculation library
For currency calculations in your own application, use the OpenForex TVX developer toolkit. It exposes TypeScript functions for direct conversion, structural valuation, feed adaptation, and validated snapshot quotes. It is a local library, not a hosted financial API; the HTTP and GraphQL interfaces documented below remain the publication interfaces.
Integration surfaces
Choose the interface that matches your task. The PHP reader and Wiki.js authoring service are separate applications with separate routes.
| Task | Interface | Local address | Authentication |
|---|---|---|---|
| Read published literature | PHP HTML reader | http://localhost/openforex.io/ | Public reading |
| Retrieve an article's Markdown | PHP download route | Reader URL with page and download parameters | Public reading |
| Query an authoring inventory | Wiki.js GraphQL | http://localhost:8080/graphql | Instance API token |
| Create or update authoring content | Wiki.js GraphQL mutations | Same GraphQL endpoint | Write access to the target pages |
The OpenForex Society's standards authority is independent of the software used to publish its literature. This reference documents the interfaces in the supplied website package. Currency conversion, issuance, balances, and settlement are outside this package's HTTP contract; no financial-service endpoint or service-level commitment is implied by these examples.
Quick start: retrieve published Markdown
The PHP reader can be used immediately when Apache serves the project directory. This request downloads the TVX literature without requiring Wiki.js:
Invoke-WebRequest `
-Uri 'http://localhost/openforex.io/?page=docs/OpenForex_Society_TVX_Neutrality_Standard_Full_Text&download=1' `
-OutFile 'openforex-tvx-standard.md'
The response is Markdown, not a JSON object. The download contains the current published text. Preserve its version and provenance when using it in another publication.
PHP reader HTTP contract
| Method and parameters | Result |
|---|---|
| GET /openforex.io/ | Homepage and document catalog as HTML |
| GET /openforex.io/?page=developer/api | This reference as HTML |
| GET /openforex.io/?page=developer/api&download=1 | Markdown attachment for this reference |
| GET with an unknown page identifier | HTTP 404 and a readable error page |
The page parameter is a catalog identifier, not an arbitrary filesystem path. Use the directory and filename without the .md extension. URL-encode it with URLSearchParams or your HTTP library. For shared hosting, replace the base address with that installation's public root; the query format is unchanged.
A successful download returns Content-Type: text/markdown; charset=utf-8 and an attachment filename. The current reader checks for the presence of the download parameter, so use download=1 consistently. A read-only JSON catalog is now available at https://openforex.io/?format=catalog. See the public publication API for its versioned contract. There is no REST write endpoint, bearer-token login, or pagination endpoint in the PHP reader. Browser search uses embedded data as a UI implementation detail.
Configure the Wiki.js authoring service
Use an initialized Wiki.js instance for the GraphQL examples below. The root PHP homepage does not start this service.
- Create .env from .env.example if it does not already exist. Preserve existing deployment settings.
- Set real database credentials. POSTGRES_PASSWORD and WIKI_DB_PASS must agree, as must the database names and usernames.
- Start the optional stack with docker compose --env-file .env up -d.
- Open the configured Wiki.js address and complete its administration setup.
- Configure the required locale and API access for the integration.
- Run a read-only query before attempting a content mutation.
| Setting | Meaning in this package |
|---|---|
| NGINX_HTTP_PORT | Host port for Wiki.js; example configuration uses 8080 |
| WIKI_PORT | Wiki.js container port; Nginx currently targets 3000 |
| WIKI_API_URL | Endpoint used by the PowerShell importer; defaults to http://localhost:8080/graphql |
| WIKI_API_TOKEN | Credential read from the invoking process environment |
| OPENFOREX_REPOSITORY_URL | PHP site's repository link; unrelated to API authentication |
The Compose .env file supplies container configuration. The PowerShell importer does not automatically parse that file; set its environment variables in the shell or supply its parameters. A hosted integration should use the operator's configured HTTPS endpoint. Localhost examples refer to your machine, not a public OpenForex service.
Authentication and permission scope
Generate a token in Wiki.js Administration under API Access. Send it in the request header, never in a URL:
Authorization: Bearer <instance-api-token>
Content-Type: application/json
Accept: application/json
Tokens need access to the requested resource and operation. Use a credential scoped to the integration's work rather than copying an administrator token into an application. Keep tokens in server-side configuration or a secret store; do not ship them in the public PHP reader's JavaScript. The upstream API authentication guide explains token creation and bearer authentication.
Permission checks also depend on the installed release and resolver. In the inspected upstream implementation, single-page lookups check page-management access, while source content has its own field permission. A token that lists page titles may therefore fail to retrieve their source. Ask the instance administrator to grant the required page-scoped access; do not assume a generic read token covers every operation. See the page resolver and the instance's own schema.
Request and response envelope
Send a POST request to /graphql containing a query string and a variables object. Keep user-controlled values in variables rather than interpolating them into GraphQL source. operationName is useful for logs and required to select an operation when a document contains more than one.
{
"operationName": "ReadStandard",
"query": "query ReadStandard($path: String!, $locale: String!) { pages { singleByPath(path: $path, locale: $locale) { id path title content updatedAt } } }",
"variables": {
"path": "tvx-formula/1.0.0",
"locale": "en"
}
}
Illustrative successful response; the identifier and timestamp below are examples, not live records:
{
"data": {
"pages": {
"singleByPath": {
"id": 42,
"path": "tvx-formula/1.0.0",
"title": "TVX Formula 1.0.0",
"content": "# TVX Formula 1.0.0\n\nExample response content.",
"updatedAt": "2026-09-24T00:00:00.000Z"
}
}
}
}
HTTP success alone is insufficient. Inspect a top-level errors array and, for mutations, responseResult.succeeded. Never infer write success merely from a page ID or from receiving valid JSON.
Page identifiers and source paths
The local PHP catalog and Wiki.js importer use different identifiers. Do not send a PHP page query as a Wiki.js page path.
| Source file | PHP page identifier | Bundled Wiki.js import path |
|---|---|---|
| seed-content/home.md | seed-content/home | home |
| seed-content/ofs/index.md | seed-content/ofs/index | ofs |
| seed-content/tvx-formula/1.0.0.md | seed-content/tvx-formula/1.0.0 | tvx-formula/1.0.0 |
| seed-content/developers/contribution-guide.md | seed-content/developers/contribution-guide | developers/contribution-guide |
| developer/api.md | developer/api | Not included in the fixed import list |
Treat path and locale together as a publication identity. Numeric page IDs belong to a particular Wiki.js database and should not be copied between staging and production. A document version such as 1.0.0 is not a page ID, API version, or revision timestamp.
Read a page by path
Use this query when your integration knows the standard's publication path. Select only the fields you need.
query ReadStandard($path: String!, $locale: String!) {
pages {
singleByPath(path: $path, locale: $locale) {
id
path
locale
title
description
content
updatedAt
tags { tag }
}
}
}
{"path":"tvx-formula/1.0.0","locale":"en"}
Do not interpret every failed lookup as an absent page. Distinguish a not-found result from an access denial, malformed query, unavailable service, or unreadable source field. The inspected upstream resolver throws a page-not-found error rather than guaranteeing a null success response. Its search operation also depends on the configured search engine. These behaviors come from the upstream resolver; verify your installed version.
List and search documents
List metadata for a bounded inventory request:
query Inventory {
pages {
list(limit: 50, locale: "en", orderBy: PATH, orderByDirection: ASC) {
id path title locale updatedAt
}
}
}
Search by topic:
query SearchStandards($term: String!) {
pages {
search(query: $term, locale: "en") {
results { id title path locale }
totalHits
suggestions
}
}
}
{"term":"neutrality"}
Search IDs are strings; page IDs are integers. Resolve a search result through its path and locale before using an ID in a mutation. The inspected list schema supports a limit but no offset or cursor; do not invent a page parameter or treat a limited response as a complete backup. See the upstream page schema.
Create a page
This mutation is for a deliberate authoring action on a selected Wiki.js instance. The example targets a sandbox path and creates an unpublished page. No mutation is executed by reading this guide.
mutation CreateReference(
$path: String!
$title: String!
$description: String!
$content: String!
$tags: [String!]!
) {
pages {
create(
path: $path
title: $title
description: $description
content: $content
tags: $tags
locale: "en"
editor: "markdown"
isPublished: false
isPrivate: false
) {
responseResult { succeeded message }
page { id path title }
}
}
}
{
"path":"sandbox/api-example",
"title":"API integration example",
"description":"A sandbox page for validating the content integration.",
"content":"# API integration example\n\nCreated for an authoring integration check.",
"tags":["integration-example"]
}
Pass an explicit tags array, including an empty array when no tags are intended. A page's publication flag concerns authoring visibility; it does not change the ratification status of OpenForex Standards. Do not treat isPublished: false as a confidentiality boundary: configure access permissions for sensitive content separately.
Update an existing page
Read the existing page and preserve fields you do not intend to change. In particular, retain its current tags rather than replacing them with an empty array. The following operation intentionally changes content, title, description, and tags, while leaving publication flags out of the request.
mutation UpdateReference(
$id: Int!
$title: String!
$description: String!
$content: String!
$tags: [String!]!
) {
pages {
update(
id: $id
title: $title
description: $description
content: $content
tags: $tags
) {
responseResult { succeeded message }
page { id path title updatedAt }
}
}
}
{
"id":42,
"title":"API integration example",
"description":"Updated sandbox integration reference.",
"content":"# API integration example\n\nRevised example content.",
"tags":["integration-example"]
}
Replace 42 with the ID returned by your instance. Re-read the page after a successful response and compare the intended fields. If the request times out, inspect the remote state before retrying: the server may have committed the change before the client lost the response.
Concurrency and publication workflow
Retain the updatedAt value from the page read. Before writing, re-read and compare it to detect another editor's changes. A changed timestamp requires review or a merge rather than a blind overwrite.
The schema also exposes checkConflicts(id, checkoutDate). A separate check followed by an update is still not an atomic compare-and-swap operation. Serialize writes to the same page or use an operator-controlled authoring window when a stronger guarantee is needed.
Keep an export of content and metadata before bulk updates. Review changes, apply them to the chosen instance, verify them, then publish through the adopted workflow. Do not equate a successful content mutation with standards approval. Publication workflow describes institutional review separately.
PowerShell client example
This read-only example uses process environment variables and a literal GraphQL string. Single-quoted here-strings preserve GraphQL dollar signs without PowerShell interpolation.
$endpoint = $env:WIKI_API_URL
$token = $env:WIKI_API_TOKEN
if (-not $endpoint -or -not $token) {
throw 'Set WIKI_API_URL and WIKI_API_TOKEN in this shell.'
}
$query = @'
query ReadStandard($path: String!, $locale: String!) {
pages {
singleByPath(path: $path, locale: $locale) {
id path title updatedAt
}
}
}
'@
$payload = @{
operationName = 'ReadStandard'
query = $query
variables = @{ path = 'tvx-formula/1.0.0'; locale = 'en' }
} | ConvertTo-Json -Depth 8
$response = Invoke-RestMethod -Method Post -Uri $endpoint `
-Headers @{ Authorization = "Bearer $token"; Accept = 'application/json' } `
-ContentType 'application/json; charset=utf-8' `
-Body ([Text.Encoding]::UTF8.GetBytes($payload)) -TimeoutSec 30
if ($response.PSObject.Properties['errors'] -and $response.errors) {
throw 'GraphQL rejected the request. Inspect the sanitized error response.'
}
if (-not $response.data.pages.singleByPath) {
throw 'No readable page was returned.'
}
$response.data.pages.singleByPath
The 30-second timeout is an example client setting, not a promised response time. Avoid logging the Authorization header or an entire page body during routine diagnostics.
TypeScript client example
Run this helper on a server or trusted integration host with fetch support. It is not a browser SDK and must not be bundled into the public wiki with a token.
type GraphResult<T> = {
data?: T;
errors?: Array<{ message: string }>;
};
export async function wikiQuery<T>(
endpoint: string,
token: string,
query: string,
variables: Record<string, unknown> = {}
): Promise<T> {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json() as GraphResult<T>;
if (result.errors?.length) {
throw new Error('GraphQL request failed; inspect sanitized diagnostics.');
}
if (result.data == null) throw new Error('Missing GraphQL data');
return result.data;
}
Use the query from Read a page by path and pass its variables as an object. The generic type is a compile-time expectation, not runtime schema validation. Check required result fields before consuming data. For mutations, additionally check responseResult.succeeded; this transport helper cannot infer the success shape of every operation.
Error handling and troubleshooting
| Symptom | Interpretation | Next step |
|---|---|---|
| Connection refused | Wrong port or service is stopped | Check the instance address and container health |
| HTML instead of JSON | Request reached the PHP reader, login page, or proxy error page | Verify endpoint, redirects, and proxy routing |
| HTTP 401 or 403 | Authentication or gateway denial | Check token validity and instance access rules |
| HTTP 200 with errors | GraphQL validation, authorization, or resolver failure | Inspect error category and affected operation |
| Unknown field or argument | Installed schema differs from the example | Inspect that instance's schema and deployed release |
| Non-null variable error | A required value or list was omitted | Supply all required variables, including tags on create |
| Page-not-found error | Path or locale may be wrong, or page may be absent | Verify exact identity and permissions before creating |
| responseResult.succeeded is false | Mutation was rejected or failed | Inspect the message and re-read the page before retrying |
| Empty search results | No match, access filtering, or search configuration | Compare with a permitted list query |
| New local article missing in Wiki.js | Import list or synchronization gap | Check route mapping and the actual import inventory |
| Changed text but old search results | Indexing or rendering may lag or have failed | Check server logs and search configuration |
Record the operation name, destination host, timestamp, HTTP status, and a redacted error category. Do not expose tokens, confidential source text, or private user information in public issue reports.
Retries, limits, and caching
This package does not define a public API quota or rate-limit guarantee. Respect limits configured by the instance operator and any reverse proxy. Keep inventory requests bounded and avoid repeatedly downloading full source when metadata is sufficient.
Use bounded exponential backoff with jitter for transient read failures. Do not retry invalid credentials or schema errors unchanged. Do not automatically retry mutations after an ambiguous timeout. Inspect whether the intended change already exists, and reconcile it before a further write. No idempotency-key mechanism is established by this guide.
If caching, key entries by instance, path, locale, and authorization context. Never share private results across users or credentials. Use the returned updatedAt value as metadata for your own comparison; the PHP reader does not document an ETag or conditional-download contract.
Bundled seed utility
Run a preview from the project directory:
.\scripts\seed-pages.ps1 -DryRun
It checks and lists 16 starter pages without making API calls. Its file list is fixed: the PHP reader's later articles are not automatically included. Preserve a reviewed path map when adding content.
The utility's write operations set isPublished to true and isPrivate to false and replace content and metadata from the local definitions. It is not a two-way synchronization engine, conflict resolver, backup utility, or dry-run diff against the server. PHP query links and the local QASH glyph notation also require adaptation before publishing in Wiki.js.
The dry-run workflow has been tested locally. Live synchronization has not been verified against a running Wiki.js instance. In particular, the utility assumes a missing-page lookup can yield null, whereas the inspected upstream resolver throws PageNotFound. Review that behavior against the target release before using the helper to create missing pages; do not turn every GraphQL error into a create request.
Schema versions and verification
The Compose image reference selects Wiki.js major version 2, not an immutable patch release. Record the actual deployed version or image digest with integration test results. Check the instance's schema before rollout and repeat compatibility checks after upgrades.
This reference was checked against the local PHP reader and importer plus the upstream GraphQL guide, page schema, and page resolver. Upstream main is a reference snapshot, not a claim that your installation runs that exact code.
Before enabling writes, verify a permitted read, a denied read, a missing page, an unpublished sandbox create, a controlled update, and an application-level failure on your staging instance. Confirm backups, token scope, link conversion, and the post-write verification process.
Continue with architecture, developer onboarding, source repository, and publication workflow.
Sources & citation
- Material type
- Developer reference
- Publisher
- OpenForex Society
OpenForex Society. “OpenForex documentation API.” https://openforex.io/?page=developer%2Fapi. Include your access date when citing.
Download the exact published MarkdownText fingerprint (SHA-256)
e1633245f0969c321ab9620b0da45ea509bc827837dd42b24ab4b048b1207e6eThis identifies the downloadable text, not an independent approval or a permanent archived edition.
Referenced material (9)
- OpenForex TVX developer toolkitProject reference
- public publication APIProject reference
- GraphQL guideExternal reference
- page resolverExternal reference
- page schemaExternal reference
- publication workflowProject reference
- architectureProject reference
- developer onboardingProject reference
- source repositoryProject reference
Standards, implementation references and worked calculations identify their sources and publication status. Read our sourcing and corrections policy.
