Member extra data and custom fields

Use grouped operations for group and field names you define. Use custom-fields operations for the reserved custom_fields group. Both use the same member/resource extra-data table. They do not read/write the dynamic user.extra relationship object or Firestore automatically. Values use string storage; choose a stable group/key convention in your integration.

Member and storage context

Use the resolved member and their existing relationship to the selected resource. User ACL and shared resource, session and account restrictions apply. Send the Wallkit session in token and resource public key in resource. Firebase-enabled members also supply the matching ID token in firebase-token; omit that header for an ordinary integration.

Examples use synthetic member 42 in resource 1001. The extra-data row definition describes storage and input limits; sample runtimes describe the clients. Read stored fields after a change to detect skipped inputs.

Operations

TaskMethod / path
Read grouped member dataGET /api/v1/user/extra/data
Upsert grouped member fieldsPUT /api/v1/user/extra/data
Delete selected grouped fieldsDELETE /api/v1/user/extra/data
Read custom fieldsGET /api/v1/user/extra/data/custom-fields
Upsert custom fieldsPUT /api/v1/user/extra/data/custom-fields
Delete one custom fieldDELETE /api/v1/user/extra/data/custom-fields/{field_key}

Read grouped member data

GET /api/v1/user/extra/data

Read extra-data rows grouped by group_key for this member/resource. This table is separate from user.extra stored on the resource relationship; it can include custom_fields as one group.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

No request body.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.

Result

HTTP 200.

Field / projectionType / presenceMeaning
itemsgroup-key map / empty arrayNamed groups map to arrays of extra-data rows. No records returns []. No paginator or order guarantee.

Example

Read music_style=techno from the preferences group. See member and storage context for the synthetic member and configuration.

cURL

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/extra/data" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN
    }
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data')
request = Request(url, headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN']}, method='GET')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{
  "items": {
    "preferences": [
      {
        "id": 9001,
        "user_id": 42,
        "resource_id": 1001,
        "group_key": "preferences",
        "field_key": "music_style",
        "field_value": "techno",
        "created_at": "2030-01-01 00:00:00",
        "updated_at": "2030-01-01 00:00:00"
      }
    ]
  }
}

Alternate result

No stored rows returns items as an empty array, rather than an empty object. HTTP 200 excerpt:

{
  "items": []
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409error_get_user_extra_dataCaught read orchestration exception.Contact support if expected fields are missing.

Shared errors cover credential/context checks.

Next task

Upsert only the group/field pairs you intend to change.

Upsert grouped member fields

PUT /api/v1/user/extra/data

Create/update only supplied valid group/field pairs and dispatch a per-field event. This writes sequentially; no transaction/atomic batch guarantee. Invalid empty/overlength group/field keys are skipped silently, and untouched pairs remain. Returned items read all current groups, not just accepted inputs.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

JSON body.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.
extra_groupsbodyarray of objectsrequired; nonemptyEach object identifies one group/field pair; omitted existing pairs remain untouched.
extra_groups[].group_keybodystringnonempty; max64 after trimDynamic group name; invalid/overlength keys skip this item silently.
extra_groups[].field_keybodystringnonempty; max128 after trimDynamic field name within group; invalid/overlength keys skip silently.
extra_groups[].field_valuebodystringfor each upserted fieldString-filtered and trimmed; no action-level value-length validation or JSON-object preservation. Send a string rather than relying on missing/null coercion.

Result

HTTP 200.

Field / projectionType / presenceMeaning
itemsgroup-key map / empty arrayNamed groups map to arrays of extra-data rows. No records returns []. No paginator or order guarantee.

Example

Upsert music_style=techno in the preferences group. See member and storage context for the synthetic member and configuration.

cURL

curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/extra/data" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"extra_groups":[{"group_key":"preferences","field_key":"music_style","field_value":"techno"}]}'

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "PUT",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({"extra_groups":[{"group_key":"preferences","field_key":"music_style","field_value":"techno"}]})
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data')
body = {'extra_groups': [{'group_key': 'preferences', 'field_key': 'music_style', 'field_value': 'techno'}]}
request = Request(url, data=json.dumps(body, ensure_ascii=False).encode("utf-8"), headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN'], "Content-Type": "application/json"}, method='PUT')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{
  "items": {
    "preferences": [
      {
        "id": 9001,
        "user_id": 42,
        "resource_id": 1001,
        "group_key": "preferences",
        "field_key": "music_style",
        "field_value": "techno",
        "created_at": "2030-01-01 00:00:00",
        "updated_at": "2030-01-01 00:00:00"
      }
    ]
  }
}

Alternate result

An empty extra_groups array is rejected; it does not clear existing data. HTTP 409 excerpt:

{
  "error": "incorrect_data",
  "error_description": "Empty user extra groups data"
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409incorrect_dataMissing JSON/nonempty outer collection.Send the documented nonempty array in JSON.
409error_update_user_extra_dataCaught upsert/readback orchestration exception.Contact support; read current fields before repeating upserts because earlier items may already have changed.

Shared errors cover credential/context checks.

Next task

Read grouped fields to display stored values and detect skipped items.

Delete selected grouped fields

DELETE /api/v1/user/extra/data

Delete each selected user/resource/group/field row and dispatch per-field events. Only selected pairs are deleted; invalid/overlength keys are silently skipped. Multiple deletions are sequential, so partial changes are possible. Missing targets have no idempotent-success guarantee.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

JSON body, including the grouped DELETE request.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.
extra_groupsbodyarray of objectsrequired; nonemptyEach object identifies one group/field pair; omitted existing pairs remain untouched.
extra_groups[].group_keybodystringnonempty; max64 after trimDynamic group name; invalid/overlength keys skip this item silently.
extra_groups[].field_keybodystringnonempty; max128 after trimDynamic field name within group; invalid/overlength keys skip silently.

Result

HTTP 200.

Field / projectionType / presenceMeaning
responseordinary empty JSON responseNo action-defined result/success field; shared response metadata may still appear. This does not report deleted counts or skipped pairs.

Example

Delete the existing preferences/music_style pair. See member and storage context for the synthetic member and configuration.

cURL

curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/extra/data" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"extra_groups":[{"group_key":"preferences","field_key":"music_style"}]}'

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "DELETE",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({"extra_groups":[{"group_key":"preferences","field_key":"music_style"}]})
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data')
body = {'extra_groups': [{'group_key': 'preferences', 'field_key': 'music_style'}]}
request = Request(url, data=json.dumps(body, ensure_ascii=False).encode("utf-8"), headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN'], "Content-Type": "application/json"}, method='DELETE')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{}

Alternate result

An empty extra_groups list is rejected rather than requesting deletion of every group. HTTP 409 excerpt:

{
  "error": "incorrect_data",
  "error_description": "Empty user extra groups data"
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409incorrect_dataMissing JSON/nonempty outer collection.Send the documented nonempty array in JSON.
409error_delete_user_extra_dataCaught read/write/delete orchestration exception.Contact support; inspect current fields before repeating writes/deletes because earlier items may already have changed.

Missing delete targets can fail in the helper; no successful no-op or one final missing-target status is promised. Shared errors cover credential/context checks.

Next task

Read grouped fields to inspect what remains; an empty response is not a deletion receipt.

Read custom fields

GET /api/v1/user/extra/data/custom-fields

Read the same extra-data table limited to group custom_fields. This is a fixed group name with dynamic field keys, not a separate fixed customer schema.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

No request body.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.

Result

HTTP 200.

Field / projectionType / presenceMeaning
itemsarrayExtra-data rows limited to group custom_fields. Empty [] when none. No paginator/order guarantee.

Example

Read music_style from the reserved custom_fields group. See member and storage context for the synthetic member and configuration.

cURL

curl -X GET "${WALLKIT_API_BASE}/api/v1/user/extra/data/custom-fields" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data/custom-fields", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN
    }
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data/custom-fields')
request = Request(url, headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN']}, method='GET')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{
  "items": [
    {
      "id": 9001,
      "user_id": 42,
      "resource_id": 1001,
      "group_key": "custom_fields",
      "field_key": "music_style",
      "field_value": "techno",
      "created_at": "2030-01-01 00:00:00",
      "updated_at": "2030-01-01 00:00:00"
    }
  ]
}

Alternate result

No custom_fields rows returns an empty array. HTTP 200 excerpt:

{
  "items": []
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409error_get_user_extra_data_custom_fieldsCaught read orchestration exception.Contact support if expected fields are missing.

Shared errors cover credential/context checks.

Next task

Upsert the custom fields you intend to change.

Upsert custom fields

PUT /api/v1/user/extra/data/custom-fields

Create/update valid supplied keys in custom_fields and dispatch per-field events. Writes are sequential; empty/overlength keys skip silently. Existing omitted fields remain. The response reads all custom_fields rows.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

JSON body.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.
itemsbodyarray of objectsrequired; nonemptyObjects identify custom_fields keys to upsert; untouched fields remain.
items[].field_keybodystringnonempty; max128 after trimDynamic name; empty/overlength keys skip silently.
items[].field_valuebodystringfor each upserted itemFiltered/trimmed string; no action-level length check. Send a string; no typed JSON-value preservation guarantee.

Result

HTTP 200.

Field / projectionType / presenceMeaning
itemsarrayExtra-data rows limited to group custom_fields. Empty [] when none. No paginator/order guarantee.

Example

Upsert music_style=techno in custom_fields. See member and storage context for the synthetic member and configuration.

cURL

curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/extra/data/custom-fields" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"items":[{"field_key":"music_style","field_value":"techno"}]}'

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data/custom-fields", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "PUT",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({"items":[{"field_key":"music_style","field_value":"techno"}]})
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data/custom-fields')
body = {'items': [{'field_key': 'music_style', 'field_value': 'techno'}]}
request = Request(url, data=json.dumps(body, ensure_ascii=False).encode("utf-8"), headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN'], "Content-Type": "application/json"}, method='PUT')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{
  "items": [
    {
      "id": 9001,
      "user_id": 42,
      "resource_id": 1001,
      "group_key": "custom_fields",
      "field_key": "music_style",
      "field_value": "techno",
      "created_at": "2030-01-01 00:00:00",
      "updated_at": "2030-01-01 00:00:00"
    }
  ]
}

Alternate result

Missing, non-array or empty items is rejected; it does not clear custom fields. HTTP 409 excerpt:

{
  "error": "field_items_required",
  "error_description": "Field items is required"
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409incorrect_data / field_items_requiredMissing JSON or nonempty items array.Send the documented nonempty items array.
409error_update_user_extra_data_custom_fieldsCaught upsert/readback orchestration exception.Contact support; read current fields before repeating upserts because earlier items may already have changed.

Shared errors cover credential/context checks.

Next task

Read custom fields and verify the displayed keys, rather than assuming every input was accepted.

Delete one custom field

DELETE /api/v1/user/extra/data/custom-fields/{field_key}

Delete the matching custom_fields row for this member/resource and dispatch its deletion event. This deletes stored profile data. It has no guaranteed successful no-op for an absent key. Other fields and groups remain.

Before you call

Existing member/resource relationship and resolved user credentials. See member and storage context for shared checks and credential transport.

Request

No request body.

NameLocationTypeRequirement / defaultMeaning and constraints
resourceheaderstringrequiredPublic key for the intended resource.
tokenheaderstringrequired member contextExisting Wallkit session token.
firebase-tokenheaderstringFirebase-enabled member onlyMatching Firebase ID token; omit for ordinary resource.
field_keypathURL-encoded stringrequired; nonempty max128 after trimExisting custom_fields key; source validates the route value, not a body field.

Result

HTTP 200.

Field / projectionType / presenceMeaning
successbooleantrue after the helper/event path returns; no independent delete-result/count verification.

Example

Delete the existing custom_fields/music_style row. See member and storage context for the synthetic member and configuration.

cURL

curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/extra/data/custom-fields/music_style" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  -H "firebase-token: ${FIREBASE_ID_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch.
async function main() {
  const url = new URL("/api/v1/user/extra/data/custom-fields/music_style", process.env.WALLKIT_API_BASE);
  const response = await fetch(url, {
    method: "DELETE",
    headers: {
      "resource": process.env.RESOURCE_KEY,
      "token": process.env.USER_TOKEN,
      "firebase-token": process.env.FIREBASE_ID_TOKEN
    }
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

url = urljoin(os.environ["WALLKIT_API_BASE"], '/api/v1/user/extra/data/custom-fields/music_style')
request = Request(url, headers={'resource': os.environ['RESOURCE_KEY'], 'token': os.environ['USER_TOKEN'], 'firebase-token': os.environ['FIREBASE_ID_TOKEN']}, method='DELETE')
try:
    with urlopen(request) as response:
        print(response.status, json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))

HTTP 200 response excerpt:

{
  "success": true
}

Alternate result

An empty or over 128-character key after trimming is rejected; use an existing valid field key. HTTP 409 excerpt:

{
  "error": "incorrect_field_key",
  "error_description": "Incorrect field key"
}

Recovery

HTTP statusAPI code / shapeCauseNext action
409user_not_existsNo user/resource relationship.Establish the intended member relationship before storing data.
409incorrect_field_keyInvalid empty/overlength path key.Use a valid encoded custom field name.
409error_delete_user_extra_dataCaught read/write/delete orchestration exception.Contact support; inspect current fields before repeating writes/deletes because earlier items may already have changed.

Missing delete targets can fail in the helper; no successful no-op or one final missing-target status is promised. Shared errors cover credential/context checks.

Next task

Read custom fields to display remaining values.

Full diagram

Use the arrow keys to scroll. Escape closes this view.

Search documentation

Enter at least 2 characters.

    ↑ ↓ move through results · Enter opens · Escape closes