{"id":"69ecef9a60bb8b34fce600a9","title":"Calling The Api","path":"how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock/calling-the-api","contentMarkdown":"# 21-Apr-2026 — Calling the API\n\n_____\n\nTwo endpoints are live, both implementing the day-one contract from [#209](https://github.com/nbarrett/ngx-ramblers/issues/209). Both require an `Authorization: Bearer <your-token>` header, and the tenant in the path must match the tenant the token is scoped to.\n\n[← Back to the overview](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock)\n\n## Try it in Swagger UI\n\nThe quickest way to pull data is the interactive API docs, no code required. Go to [`/docs`](https://salesforce-mock.ngx-ramblers.org.uk/docs), click **Authorize** in the top right, paste your token (without the `Bearer ` prefix), then expand either endpoint and click **Try it out**. [Swagger UI](https://swagger.io/tools/swagger-ui/) builds the request with the right headers and shows the response inline, so you can list a tenant's members or post a consent change straight from the browser.\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/b13d3f2c-f50b-4e99-804c-b4c9941bc7eb.png)\n\n*Swagger UI at /docs: the day-one contract as an interactive console. Authorize once with your token, then expand list members or consent writeback and click Try it out to call it live.*\n\nThe same contract is published as a machine-readable [OpenAPI document](https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json) at `/api/openapi.json`, which you can feed to a client generator.\n\nIf you would rather script it or wire it into an integration, the curl recipes below hit those same two endpoints.\n\n## List members\n\nReturns every non-removed member in the tenant.\n\n```sh\nTOKEN='rsm_KT50_your-48-hex-token-here'\ncurl -sS \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  https://salesforce-mock.ngx-ramblers.org.uk/api/groups/KT50/members \\\n  | jq .\n```\n\nThe response shape is `MemberListResponse` (full schema in the [OpenAPI document](https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json)):\n\n```json\n{\n  \"totalCount\": 412,\n  \"members\": [\n    {\n      \"membershipNumber\": 3300001,\n      \"firstName\": \"Aoife\",\n      \"lastName\": \"Doyle-Adams\",\n      \"email\": \"aoife.doyle1@ngx-ramblers.org.uk\",\n      \"consents\": { \"emailMarketingConsent\": true, ... },\n      \"groupMemberships\": [ { \"groupCode\": \"KT50\", \"roles\": [\"walkLeader\"] } ],\n      \"membershipExpiryDate\": \"2026-12-31\"\n    }\n  ]\n}\n```\n\nThe same path `/api/groups/{code}/members` is used for both group and area tenants today — the route accepts whichever 2-to-6-character code your token is scoped to. The `groupCode` segment name is a known wrinkle: a parallel `/api/areas/{code}/members` (or a renamed `/api/tenants/{code}/members`) is the obvious next step, but tracking ticket [ramblers-salesforce-mock#1](https://github.com/nbarrett/ramblers-salesforce-mock/issues/1) currently keeps both kinds on the single path so existing consumers don't have to switch URLs mid-integration.\n\n### Incremental sync — `?since=`\n\nPass an ISO-8601 timestamp to get only what's changed since then:\n\n```sh\ncurl -sS \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://salesforce-mock.ngx-ramblers.org.uk/api/groups/KT50/members?since=2026-04-01T00:00:00Z\" \\\n  | jq .changes\n```\n\nThe response gains a `changes[]` array with three kinds of entry: `added`, `updated`, and `removed`. Soft-removed members (those that disappeared in a later upload) appear here with `removed`, which is how downstream syncs notice deletions without the mock having to keep dead rows in the main `members[]` array.\n\nIf `?since=` returns nothing new, check that your timestamp is earlier than the most recent ingest. Freshly ingested rows have `updatedAt` set to the ingest time; anything before that drops out of `changes[]`.\n\n### Include lapsed memberships — `?includeExpired=true`\n\nBy default the list excludes members whose membership expiry date is in the past. Add `?includeExpired=true` to get them too — useful for renewal-reminder workflows.\n\n## Writeback a consent change\n\nA single endpoint updates one or more consent flags for a specific member.\n\n```sh\ncurl -sS -X POST \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"emailMarketingConsent\": false,\n    \"source\": \"ngx-ramblers\",\n    \"timestamp\": \"2026-04-21T09:00:00Z\",\n    \"reason\": \"unsubscribe-link\"\n  }' \\\n  https://salesforce-mock.ngx-ramblers.org.uk/api/members/3300001/consent \\\n  | jq .\n```\n\nReturns a `ConsentUpdateResponse` with the updated flags and `success: true`. The change is also logged to a `consentEvents` collection, which is what an HQ-side audit would consult.\n\n| Field | Notes |\n|---|---|\n| `source` | Per the [#209 contract](https://github.com/nbarrett/ngx-ramblers/issues/209), valid values are `ngx-ramblers` or `mailman`. New consumers can be added to the enum if needed — open an issue. |\n| `timestamp` | ISO-8601. Used to break ties when two systems write competing values. |\n| `reason` | Optional free-form string for audit context. |\n| Consent flags | Any of `emailMarketingConsent`, `groupMarketingConsent`, `areaMarketingConsent`, `otherMarketingConsent` — provide only the ones you want to change. At least one must be present. The two xlsx-only flags (`postDirectMarketing`, `telephoneDirectMarketing`) are not writeable through this endpoint by design — the source of truth for those remains the Insight Hub upload. |\n\n## Common errors\n\n| Status / code | Meaning |\n|---|---|\n| **401 UNAUTHORIZED** | Token missing, malformed, revoked, or unknown. Check the `Authorization` header; generate a new token if you've lost the original. |\n| **401** with \"Token is not authorised for tenant X\" | You're calling `/api/groups/X/members` with a token generated for a different tenant. Tokens are scoped to one code only — use the right token, or generate one for the right tenant. |\n| **404 GROUP_NOT_FOUND** / **AREA_NOT_FOUND** | No tenant with that code exists. Create it first in the admin UI. |\n| **404 MEMBER_NOT_FOUND** (consent writeback) | No member with that `membershipNumber` exists in any tenant your token can reach. |\n| **400 BAD_REQUEST** | Body validation failed. The error message includes the field name and reason. |\n\n## Next\n\n[**Lifecycle scenarios →**](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock/lifecycle-scenarios)","contentHtml":"<h1>21-Apr-2026 — Calling the API</h1>\n<hr>\n<p>Two endpoints are live, both implementing the day-one contract from <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/209\">#209</a>. Both require an <code>Authorization: Bearer &lt;your-token&gt;</code> header, and the tenant in the path must match the tenant the token is scoped to.</p>\n<p><a href=\"https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock\">← Back to the overview</a></p>\n<h2>Try it in Swagger UI</h2>\n<p>The quickest way to pull data is the interactive API docs, no code required. Go to <a href=\"https://salesforce-mock.ngx-ramblers.org.uk/docs\"><code>/docs</code></a>, click <strong>Authorize</strong> in the top right, paste your token (without the <code>Bearer </code> prefix), then expand either endpoint and click <strong>Try it out</strong>. <a href=\"https://swagger.io/tools/swagger-ui/\">Swagger UI</a> builds the request with the right headers and shows the response inline, so you can list a tenant&#39;s members or post a consent change straight from the browser.</p>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/b13d3f2c-f50b-4e99-804c-b4c9941bc7eb.png\" alt=\"\"></p>\n<p><em>Swagger UI at /docs: the day-one contract as an interactive console. Authorize once with your token, then expand list members or consent writeback and click Try it out to call it live.</em></p>\n<p>The same contract is published as a machine-readable <a href=\"https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json\">OpenAPI document</a> at <code>/api/openapi.json</code>, which you can feed to a client generator.</p>\n<p>If you would rather script it or wire it into an integration, the curl recipes below hit those same two endpoints.</p>\n<h2>List members</h2>\n<p>Returns every non-removed member in the tenant.</p>\n<pre><code class=\"language-sh\">TOKEN=&#39;rsm_KT50_your-48-hex-token-here&#39;\ncurl -sS \\\n  -H &quot;Authorization: Bearer $TOKEN&quot; \\\n  https://salesforce-mock.ngx-ramblers.org.uk/api/groups/KT50/members \\\n  | jq .\n</code></pre>\n<p>The response shape is <code>MemberListResponse</code> (full schema in the <a href=\"https://salesforce-mock.ngx-ramblers.org.uk/api/openapi.json\">OpenAPI document</a>):</p>\n<pre><code class=\"language-json\">{\n  &quot;totalCount&quot;: 412,\n  &quot;members&quot;: [\n    {\n      &quot;membershipNumber&quot;: 3300001,\n      &quot;firstName&quot;: &quot;Aoife&quot;,\n      &quot;lastName&quot;: &quot;Doyle-Adams&quot;,\n      &quot;email&quot;: &quot;aoife.doyle1@ngx-ramblers.org.uk&quot;,\n      &quot;consents&quot;: { &quot;emailMarketingConsent&quot;: true, ... },\n      &quot;groupMemberships&quot;: [ { &quot;groupCode&quot;: &quot;KT50&quot;, &quot;roles&quot;: [&quot;walkLeader&quot;] } ],\n      &quot;membershipExpiryDate&quot;: &quot;2026-12-31&quot;\n    }\n  ]\n}\n</code></pre>\n<p>The same path <code>/api/groups/{code}/members</code> is used for both group and area tenants today — the route accepts whichever 2-to-6-character code your token is scoped to. The <code>groupCode</code> segment name is a known wrinkle: a parallel <code>/api/areas/{code}/members</code> (or a renamed <code>/api/tenants/{code}/members</code>) is the obvious next step, but tracking ticket <a href=\"https://github.com/nbarrett/ramblers-salesforce-mock/issues/1\">ramblers-salesforce-mock#1</a> currently keeps both kinds on the single path so existing consumers don&#39;t have to switch URLs mid-integration.</p>\n<h3>Incremental sync — <code>?since=</code></h3>\n<p>Pass an ISO-8601 timestamp to get only what&#39;s changed since then:</p>\n<pre><code class=\"language-sh\">curl -sS \\\n  -H &quot;Authorization: Bearer $TOKEN&quot; \\\n  &quot;https://salesforce-mock.ngx-ramblers.org.uk/api/groups/KT50/members?since=2026-04-01T00:00:00Z&quot; \\\n  | jq .changes\n</code></pre>\n<p>The response gains a <code>changes[]</code> array with three kinds of entry: <code>added</code>, <code>updated</code>, and <code>removed</code>. Soft-removed members (those that disappeared in a later upload) appear here with <code>removed</code>, which is how downstream syncs notice deletions without the mock having to keep dead rows in the main <code>members[]</code> array.</p>\n<p>If <code>?since=</code> returns nothing new, check that your timestamp is earlier than the most recent ingest. Freshly ingested rows have <code>updatedAt</code> set to the ingest time; anything before that drops out of <code>changes[]</code>.</p>\n<h3>Include lapsed memberships — <code>?includeExpired=true</code></h3>\n<p>By default the list excludes members whose membership expiry date is in the past. Add <code>?includeExpired=true</code> to get them too — useful for renewal-reminder workflows.</p>\n<h2>Writeback a consent change</h2>\n<p>A single endpoint updates one or more consent flags for a specific member.</p>\n<pre><code class=\"language-sh\">curl -sS -X POST \\\n  -H &quot;Authorization: Bearer $TOKEN&quot; \\\n  -H &quot;Content-Type: application/json&quot; \\\n  -d &#39;{\n    &quot;emailMarketingConsent&quot;: false,\n    &quot;source&quot;: &quot;ngx-ramblers&quot;,\n    &quot;timestamp&quot;: &quot;2026-04-21T09:00:00Z&quot;,\n    &quot;reason&quot;: &quot;unsubscribe-link&quot;\n  }&#39; \\\n  https://salesforce-mock.ngx-ramblers.org.uk/api/members/3300001/consent \\\n  | jq .\n</code></pre>\n<p>Returns a <code>ConsentUpdateResponse</code> with the updated flags and <code>success: true</code>. The change is also logged to a <code>consentEvents</code> collection, which is what an HQ-side audit would consult.</p>\n<table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>source</code></td>\n<td>Per the <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/209\">#209 contract</a>, valid values are <code>ngx-ramblers</code> or <code>mailman</code>. New consumers can be added to the enum if needed — open an issue.</td>\n</tr>\n<tr>\n<td><code>timestamp</code></td>\n<td>ISO-8601. Used to break ties when two systems write competing values.</td>\n</tr>\n<tr>\n<td><code>reason</code></td>\n<td>Optional free-form string for audit context.</td>\n</tr>\n<tr>\n<td>Consent flags</td>\n<td>Any of <code>emailMarketingConsent</code>, <code>groupMarketingConsent</code>, <code>areaMarketingConsent</code>, <code>otherMarketingConsent</code> — provide only the ones you want to change. At least one must be present. The two xlsx-only flags (<code>postDirectMarketing</code>, <code>telephoneDirectMarketing</code>) are not writeable through this endpoint by design — the source of truth for those remains the Insight Hub upload.</td>\n</tr>\n</tbody></table>\n<h2>Common errors</h2>\n<table>\n<thead>\n<tr>\n<th>Status / code</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><strong>401 UNAUTHORIZED</strong></td>\n<td>Token missing, malformed, revoked, or unknown. Check the <code>Authorization</code> header; generate a new token if you&#39;ve lost the original.</td>\n</tr>\n<tr>\n<td><strong>401</strong> with &quot;Token is not authorised for tenant X&quot;</td>\n<td>You&#39;re calling <code>/api/groups/X/members</code> with a token generated for a different tenant. Tokens are scoped to one code only — use the right token, or generate one for the right tenant.</td>\n</tr>\n<tr>\n<td><strong>404 GROUP_NOT_FOUND</strong> / <strong>AREA_NOT_FOUND</strong></td>\n<td>No tenant with that code exists. Create it first in the admin UI.</td>\n</tr>\n<tr>\n<td><strong>404 MEMBER_NOT_FOUND</strong> (consent writeback)</td>\n<td>No member with that <code>membershipNumber</code> exists in any tenant your token can reach.</td>\n</tr>\n<tr>\n<td><strong>400 BAD_REQUEST</strong></td>\n<td>Body validation failed. The error message includes the field name and reason.</td>\n</tr>\n</tbody></table>\n<h2>Next</h2>\n<p><a href=\"https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock/lifecycle-scenarios\"><strong>Lifecycle scenarios →</strong></a></p>\n"}