From CSV to signed certificate.
The full operator handbook for Meridian/V — the ingest schema we accept today, every rule the engine enforces, who can do what, what a certificate actually proves, and how a third party verifies one.
What Meridian/V is — and what it isn't.
Meridian/V is a validation and certification workflow for road-asset survey data. An engineer uploads a CSV; the platform runs a deterministic rule pass; a second engineer co-signs; a SHA-256-hashed certificate is issued and is independently verifiable. That's the whole loop.
- A CSV ingest aligned with the TMH-18 v5 exchange format.
- A deterministic, auditable rule engine.
- An evidence chain: every override carries a justification; every certificate carries a co-signer.
- A public, no-auth verifier so receiving authorities can trust what they receive.
- Not a pavement-management system. Meridian/V validates and signs; downstream tools model life and budget.
- Not an FWD acquisition tool. Bring your own field data.
- Not a regulator, and not a government-sanctioned product. Meridian/V is an independent private company. We digitise and apply the published TMH series; the standard owners remain COTO and the Department of Transport.
- Not a substitute for engineering judgement — the co-sign exists precisely because rules cannot capture every case.
Who uses the platform
Three audiences, with different jobs-to-be-done:
| Audience | Typical job | Lands on |
|---|---|---|
| Road authorities (SANRAL, provincial DoTs) | Receive verified pavement-condition data from contractors and consultants. | /verify + dashboard |
| Consulting engineers | Upload, review, and request co-sign on data they prepared on behalf of an authority. | Submission cockpit |
| Contractors / FWD operators | Hand raw survey output to the consulting engineer for review. | Submission cockpit (engineer role) |
Five steps, end-to-end.
The full submission lifecycle through the web UI. Programmatic equivalents are tracked in the API section.
1. Sign in
Access is by application during pre-pilot. Companies apply at /sign-up; on approval the primary contact receives a one-time magic link to set their admin password and can then invite the rest of the team. Magic links carry a 24-hour TTL — if it expires, request a fresh invite from your org admin.
2. Upload a submission
Go to Submissions → New submission, give the dataset a title, attach the CSV, and click Upload & validate. File requirements:
- UTF-8 encoded (no BOM preferred)
- ≤ 50 MB
- Required headers (case-insensitive):
ROAD_NUMBER, SECTION_ID, CHAINAGE, DATE, D0, D200 - One SECTION_ID per submission for cleanest results (see Troubleshooting)
Full reference: CSV format & headers ↓.
3. Review findings
The validation engine runs synchronously on upload (typical: under 2 seconds for files under 10 MB). The cockpit shows a finding card per triggered rule, each with severity, internal rule reference, the human message, and the affected row count. ERR findings block co-sign; WRN findings can be overridden with a written justification — every override is timestamped and pinned to your user record in the audit log.
4. Co-sign & certify
When the dataset is clean (or all errors carry an override), click Request co-sign. A user with the principal_engineer role in your org receives a notification and can approve or reject. On approval, a SHA-256 hash is computed over the stored file bytes and a certificate ID is published. Final certification is gated by separation of duty: the certifier must differ from both the uploader and the cosigner.
5. Verify
Anyone — no login required — verifies a certificate at /verify. The verifier returns the dataset metadata, signers, issued time, and SHA-256 hash so a third party can re-hash their copy of the file and confirm a match. See the Public verifier section for the exact procedure.
States, gates, and who acts.
Every submission moves through a finite state machine. The states are visible in the audit log and on the submission detail page; transitions are append-only.
| State | Meaning | Next action |
|---|---|---|
| DRAFT Draft | File uploaded; row 0 of validation has run and basic format checks passed. | Engineer reviews findings and either fixes the source file (and re-uploads as a new submission) or proceeds. |
| VALIDATED Validated | All ERR-severity findings are clear; any WRN findings have either been resolved or carry an override justification. | Engineer requests co-sign. |
| AWAITING_COSIGN Awaiting co-sign | A request has been sent to the org's principal_engineer pool. Email notification dispatched. | A principal_engineer (other than the submitting engineer) approves or rejects. |
| CERTIFIED Certified | Co-signature recorded. SHA-256 computed over stored file bytes. Certificate ID issued and published to the verifier. | Optional — share the certificate ID with the receiving authority. |
| WITHDRAWN Withdrawn | A principal_engineer has revoked a previously-issued certificate. The certificate row remains queryable; the verifier marks it WITHDRAWN and exposes the reason. | Re-submit a corrected dataset as a new submission. |
| REJECTED Rejected | Co-sign was declined. The submission stays editable so the engineer can address the technical concern. | Engineer addresses the rejection note and re-requests co-sign. |
Typical timing
- Upload → DRAFT: sub-second to 2 seconds for a 10 MB file.
- DRAFT → VALIDATED: as long as it takes the engineer to review findings.
- VALIDATED → AWAITING_COSIGN: instant on click; an email is dispatched within a minute.
- AWAITING_COSIGN → CERTIFIED: bound by your principal engineer's response time, not platform latency.
The exact ingest schema.
Meridian/V's current ingest is a strict subset of TMH-18 v5. Required columns must be present (case-insensitive). Recognised optional columns are stored verbatim and surfaced in findings; unknown columns are preserved in the file but not interpreted.
Required headers
| Header | Type | Unit | Description |
|---|---|---|---|
| ROAD_NUMBER | string | — | National or provincial road designation, e.g. N1, R21, M3. |
| SECTION_ID | string | — | Authority section identifier the chainage is measured against. |
| CHAINAGE | decimal | km | Distance from section start. Must be strictly increasing within a section. |
| DATE | ISO-8601 date | YYYY-MM-DD | Survey date for the row. UTC, no time component required. |
| D0 | decimal | µm | Centre deflection under the standard 40 kN load, temperature-uncorrected. |
| D200 | decimal | µm | Deflection at the 200 mm offset sensor, used in the D0/D200 sanity ratio. |
Recognised optional headers
| Header | Type | Unit | Description |
|---|---|---|---|
| D300, D450, D600, D900 | decimal | µm | Additional FWD geophone offsets. Recognised by the parser; not yet rule-checked. |
| LOAD_KN | decimal | kN | Applied falling-weight load. Typically 40 kN; recorded for traceability. |
| TEMP_AIR | decimal | °C | Ambient air temperature at measurement time. |
| TEMP_SURFACE | decimal | °C | Pavement surface temperature at measurement time. |
| TEMP_CORRECTION | decimal | multiplier | Per-row temperature correction coefficient. If missing, the engine raises the TEMP-CORRECTION warning. |
| LANE | string | — | Lane code (e.g. L1, L2). Free-text in current ingest. |
| DIRECTION | string | N/S/E/W | Travel direction at chainage. |
| OPERATOR_ID | string | — | Operator or crew identifier for traceability. |
Example file
A minimal valid file (truncated). The full sample is shipped in the repo as sample-tmh18.csv:
Encoding & line endings
- Encoding: UTF-8. UTF-16, Windows-1252, and Latin-1 are rejected with the ENCODING-UTF8 error.
- Line endings: LF or CRLF, both accepted.
- Delimiter: comma. Semicolon-delimited exports (common in continental tooling) are not currently supported.
- Quoting: RFC 4180. Embed commas inside double-quoted fields; escape internal quotes by doubling.
- Empty cells: empty string, not
NULLorNaN. The optional-column rules treat empty as absent.
Every rule, every fix.
The engine runs five rules on every upload. Internal rule references are stable identifiers — you can cite them in support emails or audit reports. Rules will be added as additional TMH-18 sections are implemented; once added, a rule reference is never reused for a different check.
When it fires
The uploaded file contains the Unicode replacement character (U+FFFD), indicating it was not stored as valid UTF-8 — typically Windows-1252 / Latin-1 saved without conversion.
Why it matters
TMH-18 exchange files are required to be UTF-8 so that section labels containing the § sigil and other authority-specific glyphs are not silently corrupted between systems.
How to fix
Re-export from the survey software with UTF-8 encoding, or open the file in a modern editor (VS Code, Notepad++) and "Save As → UTF-8" before re-uploading.
When it fires
One or more of the required headers (ROAD_NUMBER, SECTION_ID, CHAINAGE, DATE, D0, D200) is missing from row 1.
Why it matters
Without the canonical header set, the engine cannot map column positions to schema fields and validation cannot proceed.
How to fix
Add the missing column(s) to the header row. Header names are case-insensitive and trimmed, so MR_NUMBER vs Road_Number is fine — but the underlying token must match.
When it fires
A row's CHAINAGE value is less than or equal to the previous parsed value, suggesting a section was traversed in the wrong direction or rows were re-ordered.
Why it matters
Chainage is the spatial primary key for road-asset data. Non-monotonic chainage typically indicates a directional flip, a duplicate row, or a malformed section join, all of which break downstream segment aggregation.
How to fix
Sort rows by SECTION_ID then CHAINAGE before export. If you intentionally surveyed in reverse direction, post-process the chainage to be increasing along the SECTION_ID frame of reference.
When it fires
For one or more rows, D0 / D200 falls outside the [1.0, 8.0] sanity range.
Why it matters
A ratio below 1.0 is physically implausible (the offset deflection cannot exceed the centre deflection on a uniform pavement); above 8.0 typically indicates a sensor calibration drift, an underlying bedrock anomaly, or a transcription error rather than real pavement behaviour.
How to fix
Inspect the flagged rows. If real, attach a justification when overriding (e.g. known geotechnical anomaly at the chainage). Otherwise, re-run the FWD with calibration verified.
When it fires
The TEMP_CORRECTION column is missing entirely, or one or more rows have an empty TEMP_CORRECTION cell.
Why it matters
Deflection values are temperature-sensitive. Without a per-row correction coefficient, downstream pavement-life models become unreliable across surveys collected in different seasons.
How to fix
Add a TEMP_CORRECTION column derived from your standard correction model (or the authority-supplied lookup) for every row. Where temperature data is intentionally not collected, override with a justification noting the survey context.
Severity semantics
| Code | Meaning | Effect on co-sign | Override |
|---|---|---|---|
| ERR | Schema or structural violation. The data cannot be trusted. | Hard block. | Not permitted. |
| WRN | Plausibility / completeness concern. The data may still be valid in context. | Soft block — clears with override. | Permitted with written justification. |
Rules on the roadmap
The following are tracked but not yet shipped. Order is indicative, not committed.
- SECTION-CONTINUITY — chainage continuity across SECTION_ID joins, not just within a single section.
- DATE-WINDOW — DATE column must fall within an authority-supplied survey window per submission.
- LOAD-TOLERANCE — LOAD_KN within ± 5% of declared nominal (40 kN by default).
- SENSOR-COMPLETENESS — at least four of D0, D200, D300, D450, D600, D900 populated per row.
- OPERATOR-CONTINUITY — single OPERATOR_ID per (SECTION_ID, DATE) combination unless explicitly noted.
- VISUAL-MATCH — when a TMH-9 visual assessment file is co-submitted, cross-reference distress codes.
Who can do what.
Permissions are role-based at the org level. A user has exactly one role at a time within an org. Cross-org access is reserved for super_admin, and even then only for platform-operations tasks, not customer data inspection.
- Invite and remove users
- Change user roles within the org
- View and configure org settings
- Submit datasets
- Withdraw a submission before co-sign
- Co-sign a certificate (separation of duty)
- View other organisations' data
- Upload submissions
- Review and override warnings with a justification
- Withdraw a submission they own before co-sign
- Request co-sign
- Approve their own co-sign request
- Invite users
- Modify another user's submission
- Receive co-sign requests
- Approve or reject a co-sign
- Annotate the certificate with technical notes
- Withdraw a certificate (with reason; produces a public revocation record)
- Co-sign a submission they themselves uploaded
- Modify the underlying file bytes once uploaded
- Approve or reject incoming organisation applications
- Suspend an organisation
- View global audit log
- Promote a user to super_admin via the bootstrap script
- Co-sign on behalf of a customer org
- Read submission file bytes outside an explicit incident-response procedure
What a certificate proves.
A Meridian/V certificate is a tamper-evident attestation that at a specific time, two named engineers signed off on a specific dataset whose bytes hashed to a specific value. It is not a statement about the truth of the underlying pavement measurements — that responsibility remains with the engineers — but it is a strong statement about provenance and immutability.
What a certificate contains
- Certificate ID — 12-character base32, unique platform-wide.
- Dataset metadata — title, organisation, original filename, row count, byte length.
- Submitter identity — name and role at time of upload.
- Co-signer identity — name and role at time of approval.
- Override summary — count and rule references of any warning overrides applied.
- Issued-at timestamp — UTC, ISO-8601, set by the database at the moment the co-sign succeeds.
- SHA-256 hash — hex-encoded digest computed over the stored file bytes (not the original upload bytes — see below).
The hash
The hash is computed over the bytes of the file as stored in the platform's object store — not the bytes the browser uploaded. In practice these are identical (Meridian/V does not transcode), but the distinction matters: the certificate binds to what we have, which is what a third party will re-download to verify.
Immutability and withdrawal
Certificate fields are not editable after issue. If a defect is discovered, a principal_engineer can withdraw the certificate — this does not delete the record; instead the verifier surfaces aWITHDRAWN banner with the withdrawal reason and timestamp. Receiving authorities should treat a withdrawn certificate as untrustworthy and request a fresh submission.
What a certificate is not
- It is not a digital signature in the X.509 / PKCS#7 sense. There is no per-engineer signing key — yet. Adding ECDSA keys per principal_engineer is on the roadmap.
- It is not a guarantee that the underlying field measurements are accurate; it is a guarantee that the file you re-hash is byte-identical to the file two engineers signed off on.
- It is not a substitute for the authority's own acceptance procedures. Authorities still own the gate decisions on their road network.
How a third party checks a certificate.
The verifier at tmh18.com/verify is unauthenticated and read-only. Anyone with a certificate ID can confirm the binding. Three-step procedure:
- Open /verify, paste the certificate ID, submit.
- Note the SHA-256 hash and dataset metadata returned by the verifier.
- On your own machine, compute SHA-256 of the file copy you hold and compare. A bit-for-bit match means the file you have is the file the engineers signed off on.
Verifier response — exact fields
| Field | Example | Use |
|---|---|---|
| certificateId | K4Q9F2X7M3R8 | Sanity check — matches the ID you entered. |
| status | VALID | WITHDRAWN | Reject WITHDRAWN; treat the dataset as untrustworthy. |
| issuedAt | 2026-05-04T13:21:55Z | UTC ISO-8601 — when the co-sign was recorded. |
| organisation | Acme Pavement Consultants | Submitting org — confirm against the engagement letter. |
| submitter | D. Seyoum (engineer) | Who uploaded. |
| cosigner | P. Mthembu (principal_engineer) | Who approved. |
| filename | n1-section14-2026-04.csv | Original filename at upload time. |
| byteLength | 4 218 902 | Bytes — quick mismatch detector before re-hashing. |
| sha256 | e3b0c442…b855 | Compare against your local hash. |
| overrides | 1 × DEFLECTION-RATIO | Disclose what warnings were overridden, by whom, with what justification. |
REST API v1.
The /api/v1 surface is read-only today and covers identity, schemas, submissions, findings and PDF reports. Auth is a bearer token minted at /api-tokens. All responses are JSON unless explicitly noted (the PDF report endpoint returnsapplication/pdf). Base URL on production: https://tmh18.com/api/v1.
Authentication
Mint a token from API tokens → Generate. The plaintext token (format mv_live_…) is shown once; only the SHA-256 hash is stored, so re-display is impossible. Pass it as a bearer header on every request:
Authorization: Bearer mv_live_<your token>
Tokens are scoped to the org of the user that minted them, carry no per-user identity, and inherit no privileges beyond the v1 read surface listed below. Revoke from the same page; revocation is immediate (cache-free lookup).
Rate limits
Limits are enforced per token, per minute:
| Class | Limit | Applies to |
|---|---|---|
| Read | 120 req / minute | GET endpoints |
| Write | 30 req / minute | Reserved for future POST endpoints |
Every response carries RateLimit-Limit and RateLimit-Policy advisory headers. When you exceed the quota you get HTTP 429 with Retry-After (seconds) — wait and retry; do not tight-loop.
Error envelope
Every 4xx and 5xx response uses the same shape:
{ "error": "rate_limited",
"message": "Rate limit exceeded for this token (120 reads / minute). Retry after 42s." }Common codes:
| HTTP | error | When |
|---|---|---|
| 401 | missing_token | No Authorization header |
| 401 | invalid_token_format | Doesn't start with mv_live_ |
| 401 | invalid_token | Hash not found or revoked |
| 400 | invalid_status | Unknown status query param |
| 404 | not_found | Resource not in your org |
| 429 | rate_limited | Per-token bucket exhausted |
Endpoints (v1)
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/me | Identity check: returns org + token info. Use as connectivity test. |
| GET | /api/v1/schemas | List schemas (optional ?state=stable). |
| GET | /api/v1/schemas/{id} | Single schema with full field definition. |
| GET | /api/v1/submissions | List org submissions. Params: status, schema_code, limit (1–200, default 50), cursor. |
| GET | /api/v1/submissions/{id} | Submission detail + validation run findings + certificate. |
| GET | /api/v1/submissions/{id}/report | PDF report (application/pdf). Same content as the in-app Download PDF button. |
POST / write endpoints (create submission, apply override, request co-sign, webhooks) ship after the read surface is exercised by pilot users. Token format and auth header will not change.
Python quickstart
Standard library + requests. Install once: pip install requests. Replace YOUR_TOKEN with the plaintext token you copied at mint time.
import os, requests
BASE = "https://tmh18.com/api/v1"
TOKEN = os.environ["MERIDIANV_TOKEN"] # never hardcode
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {TOKEN}",
"Accept": "application/json",
})
# 1. Connectivity / identity
r = session.get(f"{BASE}/me", timeout=10)
r.raise_for_status()
print(r.json())
# → {"org": {"id": "...", "code": "FSP", "name": "Free State DoR"},
# "token": {"id": "...", "name": "CI pipeline"},
# "api": {"version": "v1"}}Python: paginate submissions
def iter_submissions(status=None, schema_code=None, page=50):
cursor = None
while True:
params = {"limit": page}
if status: params["status"] = status
if schema_code: params["schema_code"] = schema_code
if cursor: params["cursor"] = cursor
r = session.get(f"{BASE}/submissions", params=params, timeout=10)
r.raise_for_status()
body = r.json()
for s in body["data"]:
yield s
cursor = body.get("next_cursor")
if not cursor:
return
# Pull every certified submission for your org
for s in iter_submissions(status="certified"):
print(s["id"], s["title"], s["validation"])Python: download the PDF report
def fetch_report(submission_id, out_path):
r = session.get(f"{BASE}/submissions/{submission_id}/report",
headers={"Accept": "application/pdf"},
stream=True, timeout=60)
r.raise_for_status()
with open(out_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
fetch_report("cmotz77az0009s8f5kwb45pmy", "report.pdf")Python: retry on rate-limit
import time
def get_with_retry(path, params=None, max_attempts=3):
for attempt in range(max_attempts):
r = session.get(f"{BASE}{path}", params=params, timeout=10)
if r.status_code != 429:
r.raise_for_status()
return r.json()
retry_after = int(r.headers.get("Retry-After", "5"))
time.sleep(retry_after)
raise RuntimeError("rate-limited after retries")
print(get_with_retry("/schemas", {"state": "stable"}))cURL one-liner
curl -sS -H "Authorization: Bearer $MERIDIANV_TOKEN" \
https://tmh18.com/api/v1/me | jqBest practices
- Store tokens in env vars or a secrets manager — never commit them to git.
- Mint one token per integration (CI, BI dashboard, partner sync). Revoke individually if a system is decommissioned.
- Set a 10–60 s request timeout; PDF report can take longer. Use connection pooling (
requests.Session). - Honour
Retry-After. Do not tight-loop on 429. - Treat 4xx responses as terminal (fix the request); only retry 429 and 5xx.
How the platform handles your data.
Authentication
- User identity runs on a managed identity provider with mandatory MFA support.
- New users are onboarded via 24-hour magic-link invites, not plaintext passwords.
- Sessions are short-lived JWTs; the signing secret is rotated on a documented schedule.
- Optional MFA (TOTP) is in roll-out and will be mandatory before commercial launch.
Storage & encryption
- Submission files: encrypted object storage with server-side AES-256 encryption and versioning enabled.
- Metadata, audit log, certificates: managed PostgreSQL 16 with encryption at rest.
- In transit: TLS 1.2+ end-to-end. HSTS enforced on tmh18.com.
Data residency
Pilot data is processed on managed cloud infrastructure under a written data-processing agreement and a POPIA-aligned handling regime. Migration to a South African region is on the roadmap before commercial launch so customer data resides in-country. Pilot organisations are informed of the current region in writing at onboarding.
POPIA posture
- Lawful basis: contract performance with the submitting organisation.
- Data minimisation: the platform only collects user identifiers needed to operate the workflow (name, email, org, role). No location tracking, no behavioural profiling.
- Subject rights: deletion and export requests are routed to privacy@tmh18.com.
- Information Officer registration: pending operating-entity finalisation.
Email & anti-spoofing
- SPF: an explicit allow-list of approved outbound senders,
-allhard-fail on tmh18.com. - DKIM: 2048-bit signing, three CNAMEs published.
- DMARC: tightening from
p=nonetop=quarantineafter the first week of clean reports, thenp=reject. - Companion domain tmh18.app is locked down with SPF
-alland DMARCp=reject— no mail is sent from it.
Reporting a vulnerability
Report security issues to security@tmh18.com. We acknowledge within one business day and coordinate disclosure with the reporter. Please do not include exploit payloads in initial outreach; we'll arrange a secure channel.
Common stumbles, and the fix.
If your symptom isn't here, email admin@tmh18.co.za with the submission ID and a screenshot of the findings cockpit. We respond within one business day during pilot.
Upload fails with "file too large".
Submissions are capped at 50 MB. Most field surveys exceeding this are multiple sections concatenated.
FIXSplit the export by SECTION_ID and submit one section per submission, or filter to the section the authority has actually requested.
"Missing required headers" with all six columns visibly present.
The header row contains a UTF-8 byte-order mark (BOM) glued to ROAD_NUMBER. Excel inserts this when saving as "CSV UTF-8".
FIXSave as plain CSV (no BOM) from your editor, or pre-strip with `sed -i $'1s/^\xEF\xBB\xBF//' file.csv`. The parser will tolerate this in a future release.
CHAINAGE-MONOTONIC fires on a clean ascending file.
A SECTION_ID change resets chainage to a lower value but the engine currently scans rows in file order, not grouped by section.
FIXUntil per-section grouping ships, submit one SECTION_ID per file, or interleave a small offset to keep chainage strictly increasing across the section boundary.
DEFLECTION-RATIO fires on every row.
Units are wrong — D0 / D200 are recorded in mm rather than µm, or the sensor offsets are reversed in the export.
FIXConfirm both columns are in micrometres (µm) and that D200 corresponds to the 200 mm-offset geophone, not centre.
Co-sign request never arrives.
No user in the organisation has the `principal_engineer` role, or the existing principal_engineer is the same user as the submitter.
FIXAn admin must promote at least one other user to principal_engineer. The platform enforces separation of duty: a submitter cannot self-sign.
Verifier returns "certificate not found" for a freshly-issued ID.
Certificate IDs are case-sensitive and the trailing dash variant is sometimes copied without the suffix.
FIXCopy the ID using the "copy" button in the certificate detail view rather than selecting text manually. IDs are 12 base32 characters.
Terms used in this manual.
- TMH-18
- Technical Methods for Highways manual #18, "Road Asset Data Electronic Exchange Formats", published by the Committee of Transport Officials (COTO) on behalf of the Department of Transport, South Africa. Defines the columnar exchange schema for road condition and pavement-survey data.
- COTO
- Committee of Transport Officials — inter-governmental committee that owns the TMH series.
- FWD
- Falling Weight Deflectometer — a trailer-mounted device that drops a calibrated weight onto a load plate and records pavement deflection at a row of geophones (D0 at centre, D200 / D300 / D450 / D600 / D900 at offsets in millimetres).
- Chainage
- Distance along a road from the section start, conventionally in kilometres. Acts as the spatial primary key in TMH-18 exchange files.
- Deflection ratio
- D0 / D200 — used as a quick-look indicator of pavement layer competence. Healthy ratios sit between 1 and 8; values outside trigger a sanity warning.
- Co-sign
- The Meridian/V workflow gate in which a principal_engineer (distinct from the submitter) approves a validated dataset, after which a SHA-256 signed certificate is issued.
- Certificate
- An immutable record consisting of: certificate ID, dataset metadata, submitter and co-signer identities, issued time, and a SHA-256 hash over the stored file bytes. Verifiable at /verify with no login.
- POPIA
- Protection of Personal Information Act, 2013 (South Africa). Establishes the lawful basis and conditions for processing personal information in South Africa, including data residency expectations.
- Audit log
- Append-only record of platform actions: uploads, overrides, role changes, co-sign requests/approvals/rejections, certificate withdrawals. Visible to org admins for their own org.
Where to ask.
Pilot orgs have direct email access to the team. We aim for a one-business-day first response; actual time-to-fix depends on the issue.
admin@tmh18.co.za
Submission help, validation findings, account access, role changes.
security@tmh18.com
Vulnerability reports. Coordinated disclosure; one-business-day acknowledgement.
privacy@tmh18.com
Subject access, deletion, and export requests under POPIA.
accounts@tmh18.com
Pricing, contracting, post-pilot transition.
Last updated 2026-05-05. This page is regenerated whenever a rule, role, or workflow gate ships. Track changes alongside the platform release notes.