Content access

Follow the access walkthrough for an ordered integration. Choose the check for allow/deny, sync-and-check for intended registration of a missing item, details for limits, or the list for content already linked to a member. Business terms explain the context.

Visitor and content context

Use the supplied API base and resource public key. A member uses the Wallkit token, with a matching Firebase ID token when required. A permitted guest branch uses a stable guest session; the resource key alone is insufficient. Each operation states its active-user guards and guest-plan requirements.

Shared context errors differ from a returned allow:false decision. A check can record views/activity; sync-and-check can create records before denial. Details and linked-content lists have different purposes. Read the local effect and recovery before another request. Examples follow the sample conventions.

Operations

OperationMethod / path
List content linked to the userGET /api/v1/user/content
Check content accessGET /api/v1/user/content/{key}
Sync missing content and check accessGET /api/v1/user/content-sync-and-check/{key}
Read resource access detailsGET /api/v1/user/content-access-details
Read access details for contentGET /api/v1/user/content-access-details/{key}

List content linked to the user

GET /api/v1/user/content

List distinct content records linked to this user through content relationships within the selected resource. This is not a list of every content item a plan might allow, and it does not run a new access decision.

Before you call

Use an existing member token. Returned items need a user-content relationship; the list is not a complete resource catalog or all plan-accessible content. See visitor and content context for supplied configuration and shared checks.

Request

GET with no request body or request Content-Type requirement. Response media type is JSON. Requires the user role or inherited permission and active member context. See credential transport.

NameLocationTypeRequirement / defaultMeaning and constraint
resourceheaderstringrequired contextPublic resource key; not the secret.
tokenheaderstringmember contextExisting member-session token.
firebase-tokenheaderstringFirebase-enabled member contextConfigured Firebase ID token alongside the Wallkit token.
pagequeryintegeroptionalUse 1 for the first page; pass it explicitly. No explicit omitted-page default in the operation.
limitqueryintegeroptionalDefault 10. No universal maximum is specified.
filterqueryobject / JSON stringoptionalModel-field filters; bracket notation or JSON string. Text fields use case-insensitive substring matching; numeric arrays use membership.
order / byquerystringoptionalorder takes precedence over by; default indicated below.
sortquerystringoptionalDefault DESC; use ASC or DESC.

Filters span Content and UserContentRelationship. Sorting defaults to Content.created_at DESC; order overrides by. No new allowed-view record is created by the list.

Result

HTTP 200 JSON.

Field / projectionType / presenceMeaning
items[]content with term arraysUse the field definitions. This linked-user list does not enumerate every item that a plan permits.

paginator uses the collection fields. An empty collection is an ordinary result; do not interpret it as an authentication failure.

Example: list content linked to the user

List article-1001 already linked to this member. The taxonomy fields contain arrays of terms. Supply the sample configuration and runtimes. These three requests are equivalent.

cURL

curl "${WALLKIT_API_BASE}/api/v1/user/content?page=1&limit=10" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch. Supply existing integration configuration.
async function main() {
  const url = new URL("/api/v1/user/content", process.env.WALLKIT_API_BASE);
  url.searchParams.set("page", "1");
  url.searchParams.set("limit", "10");
  const headers = { resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN };
  const response = await fetch(url, { method: "GET", headers });
  const body = await response.json();
  console.log(response.status, body);
}
main().catch(console.error);

Python

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

url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content")
url += "?" + urlencode({'page': '1', 'limit': '10'})
headers = {"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}
request = Request(url, headers=headers, 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": 1001,
      "key": "article-1001",
      "title": "An introduction to synthesis",
      "price": 0,
      "currency": "USD",
      "content_type": "article",
      "taxonomies": [
        {
          "key": "topic",
          "title": "Topic",
          "terms": [
            {
              "key": "music",
              "title": "Music"
            }
          ]
        }
      ]
    }
  ]
}

Alternate result

No matching records on this page. Show an empty state; review the member/resource context or filters if unexpected. Do not treat an empty list as permission or as an API error.

Response excerpt:

{
  "items": []
}

Recovery

HTTP statusAPI code / responseCauseNext action
401auth_failedRequired identity is missing.Supply the existing member token and check its selected resource context.
401auth_access_failInactive account or locked/suspended resource relationship.Ask the administrator to check account activation and the resource relationship’s lock/suspension state.
401 / 403accessRole does not permit the operation.Use an identity permitted for this action; ask the administrator to check the assigned role.
404resource_not_existsRequired resource is missing or unknown.Check the resource public key and selected integration context with the administrator.

See visitor and content context for applicable shared checks; follow the local error table above.

Next task

Check access to a chosen item to obtain the current decision rather than treating list membership as permission.

Check content access

GET /api/v1/user/content/{key}

Use this call immediately before deciding whether your application should serve an existing content item. Read allow from the result; HTTP 200 alone is not permission. Metadata lookup describes an item but does not check the visitor’s entitlement.

This GET can record an allowed view and access/paywall activity. Repeated calls can affect later view/limit decisions. It does not create missing content; do not poll it as a read-only metadata endpoint or blindly retry it.

Before you call

A resource is a publication/integration context; a content key identifies an item inside it. Use the resource public key and API base supplied for your integration, a known content key from your publishing integration or metadata record, and an existing member token. The item must exist in that resource. For a guest, use a stable guest session instead of a member token, and the resource must have a configured default guest subscription for the guest-plan branch. Credentials explains these contexts.

This operation permits guest access, but still needs an established guest/member context. Member accounts must be active and their resource relationship must not be locked or suspended. Plan rules, prior allowed views, applicable purchases, event access and bundles can affect the decision.

Request

GET with no request body or request Content-Type requirement. Response media type is JSON.

NameLocationTypeRequirement / defaultMeaning and constraint
resourceheaderstringrequiredResource public key selecting the item and identity context; never the resource secret.
tokenheaderstringmember branchExisting Wallkit member-session token.
sessionheaderstringguest branch instead of tokenStable guest-session identifier; a resource key alone does not establish this context.
firebase-tokenheaderstringFirebase-enabled member contextSend the configured Firebase ID token alongside token; do not add it to unrelated guest requests.
keypathstringrequiredResource-scoped content key; trimmed and encoded as one path segment.
return_contentquerybooleanoptional; omitted/false adds no dataTrue includes richer content/taxonomy metadata under data. It does not change allow into a content delivery response.

Result

HTTP 200 JSON uses the full access-decision definition, including field meanings, nullable values, guest details and the optional data projection. The decision is at the top level, not in items.

When allow is true, your application may serve the requested item according to that decision. When false, withhold it and display the appropriate access/sign-in or membership UI for your integration. A Pricing displayed in the catalog is not itself a membership or a grant.

Example: a member may read the item

Scenario: article-1001 already exists, and this member has a full-access plan. Supply WALLKIT_API_BASE (scheme/host only), RESOURCE_KEY and USER_TOKEN from the existing integration. The three requests below are equivalent. See sample runtime assumptions.

cURL

curl "${WALLKIT_API_BASE}/api/v1/user/content/article-1001" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch. Supply existing integration configuration.
async function main() {
  const url = new URL("/api/v1/user/content/article-1001", process.env.WALLKIT_API_BASE);
  const headers = { resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN };
  const response = await fetch(url, { method: "GET", headers });
  const body = await response.json();
  console.log(response.status, body);
}
main().catch(console.error);

Python

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

url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content/article-1001")
headers = {"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}
request = Request(url, headers=headers, 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, success excerpt:

{
  "allow": true,
  "reason": "full_access",
  "message": "Content is available due to purchase of a plan",
  "content": {
    "key": "article-1001",
    "title": "An introduction to synthesis",
    "link": "https://example.com/articles/1001"
  },
  "guest_access": false
}

The full_access reason explains this scenario; allow, not the reason string, is the decision your application uses. The omitted signatures and other fields are defined above.

Alternate result: the member is denied

For the same resource/content key, another established member without an applicable access rule can receive HTTP 200:

{
  "allow": false,
  "reason": "user_access",
  "message": "Content is not available for member subscription",
  "content": {
    "key": "article-1001",
    "title": "An introduction to synthesis",
    "link": "https://example.com/articles/1001"
  },
  "guest_access": false
}

Withhold the article. Do not treat this as a transport failure or blindly repeat the GET: the account can be authenticated yet lack access. Use access details to explain applicable plan limits or prior views; those details are not a replacement permission decision.

Errors and recovery

HTTP statusCode / shapeCauseNext action
401auth_failedNo established active user context.Check the member token/resource pairing, or supply a stable guest session for the configured guest flow. Do not substitute a resource key for a token.
401auth_access_failGlobal account inactive or resource relationship locked/suspended.Ask the integration/account administrator to resolve account state; changing the content key does not repair it.
401 / 403accessContext does not permit this action.Check the selected identity and operation permissions; do not switch to an elevated credential just to bypass an access result.
404resource_not_exists / incorrect_resource_keyResource public key does not resolve.Check the supplied resource public key and integration context; replacing a user token does not repair the resource key.
404incorrect_content_keyEmpty key or item absent from this resource.Check the publisher-provided key and resource. If intentionally registering missing content, use the configured sync-and-check flow.
404user_access_denyAccount/resource restriction reaches the access check.Resolve the account lock/suspension with its administrator; do not interpret this as an ordinary plan denial.

Shared session recovery covers expired/compromised tokens and initialization errors. Those HTTP failures differ from the ordinary 200 allow:false decision.

Next task

Follow the content-access walkthrough to connect content metadata, allow/deny handling and optional limit details. To register a missing item before checking access, read sync-and-check, including its creation-before-denial behavior.

Sync missing content and check access

GET /api/v1/user/content-sync-and-check/{key}

Register a missing publisher item and get the visitor’s access decision in one call. Choose this enabled flow when your integration intends to create the item; use check-only for content already registered. Existing content is not overwritten from the supplied creation fields. This GET performs writes.

This GET creates a missing item and related records before checking access. A later denial does not undo creation. Existing items ignore creation fields and have their content cache cleared. It can also record allowed views/activity; do not use it for metadata polling or blind retries.

Before you call

The resource administrator must enable content_sync_and_check. Use a member token or stable guest session. For a guest-plan decision, configure a default guest subscription. Choose a resource-scoped key from the publishing integration; for a missing item supply type, title and link. See visitor and content context for supplied configuration and shared checks.

A taxonomy is a grouping dimension, such as Topic. A term is a value within it, such as Energy. Stable keys identify those records; titles provide display labels. An item can carry several dimensions and several terms within a dimension. Start with the minimal request below; then read the library input, two classification examples and precise field details when needed.

Example: sync missing content and check access

Register missing article-2001, titled A guide to drum machines, under type article. The member’s full-access plan permits it after creation. Creation sets currency USD but does not assign a price; the excerpt omits the uncertain payable flag. Supply the sample configuration and runtimes. These three requests are equivalent.

cURL

curl "${WALLKIT_API_BASE}/api/v1/user/content-sync-and-check/article-2001?type=article&title=A+guide+to+drum+machines&link=https%3A%2F%2Fexample.com%2Farticles%2F2001" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch. Supply existing integration configuration.
async function main() {
  const url = new URL("/api/v1/user/content-sync-and-check/article-2001", process.env.WALLKIT_API_BASE);
  url.searchParams.set("type", "article");
  url.searchParams.set("title", "A guide to drum machines");
  url.searchParams.set("link", "https://example.com/articles/2001");
  const headers = { resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN };
  const response = await fetch(url, { method: "GET", headers });
  const body = await response.json();
  console.log(response.status, body);
}
main().catch(console.error);

Python

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

url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content-sync-and-check/article-2001")
url += "?" + urlencode({'type': 'article', 'title': 'A guide to drum machines', 'link': 'https://example.com/articles/2001'})
headers = {"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}
request = Request(url, headers=headers, 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:

{
  "allow": true,
  "reason": "full_access",
  "message": "Content is available due to purchase of a plan",
  "content": {
    "key": "article-2001",
    "title": "A guide to drum machines",
    "link": "https://example.com/articles/2001"
  },
  "guest_access": false
}

Use the Integration Library input

These library inputs follow the publisher’s Integration Library source, with the companion SDK source. Use your existing browser integration and supplied resource configuration. The library’s browser entry exports window.WallkitIntegration; an instance exposes the content constructor as wk.content. Its setup initializes the SDK from public_key and the configured mode. Wait for the integration’s ready callback before constructing a content manager. These examples continue that established setup; they do not supply a new resource or sign-in flow.

Within that ready flow, use const item = new wk.content(content) with either object below, then await item.checkAccess(). The manager checks the existing item first; an incorrect_content_key result selects its sync-and-check fallback. Existing items therefore take the check-only path. In the library result, use allowed for the decision and data for the API response; an error is returned as {allowed: false, error}. Keep access handling separate from classification.

Use id for the content key in the request path. The content input also accepts type, title, link, image and taxonomies. Supply a clean path-safe id and an explicit link. Each taxonomy needs a label and a nonempty items array of complete slug/name pairs. Missing items can throw before a request; an empty array produces empty CSV values, rather than safely omitting a classification. The examples supply a synthetic image URL too: omitted/falsy image becomes the literal query value image=null in this source, so omission is not equivalent to leaving the REST field out.

The serializer joins labels/slugs/names into CSV strings, makes bracketed term-group names and URL-encodes the flat fields. The two REST trios below carry those same registration fields. Library setup also supplies resource/session/token headers and can request access details for an authenticated member; the REST trios show a member’s single registration request. The source revisions above establish this shape. A demo/CDN URL tagged 3.4.4 does not prove its bytes match those revisions; check the library version used by your integration.

News article: section, topics and author

Assume this item is missing in the selected resource. Its human classification tree is:

Council climate plan (article)
  Section [section]
    News [section-news]
  Topic [topic]
    Climate [topic-climate]
    Energy [topic-energy]
  Author [author]
    Robin Lee [author-robin-lee]

Use this object as content for the initialized library’s new wk.content(content) constructor. Its taxonomy keys name dimensions; each label is a taxonomy title, and each items entry maps slug to a term key and name to its title.

{
  "id": "news-2101",
  "type": "article",
  "title": "Council climate plan",
  "link": "https://example.com/news/2101",
  "image": "https://example.com/images/news-2101.jpg",
  "taxonomies": {
    "section": {
      "label": "Section",
      "items": [
        {
          "slug": "section-news",
          "name": "News"
        }
      ]
    },
    "topic": {
      "label": "Topic",
      "items": [
        {
          "slug": "topic-climate",
          "name": "Climate"
        },
        {
          "slug": "topic-energy",
          "name": "Energy"
        }
      ]
    },
    "author": {
      "label": "Author",
      "items": [
        {
          "slug": "author-robin-lee",
          "name": "Robin Lee"
        }
      ]
    }
  }
}

The three requests below carry the same fields. The JavaScript query object is ordinary HTTP request data; each bracketed key names one CSV group in the REST query. An administrator could configure a Plan rule for the Topic → Energy classification. Attaching this article to Energy does not create that rule or grant the visitor access.

cURL

curl -G "${WALLKIT_API_BASE}/api/v1/user/content-sync-and-check/news-2101" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  --data-urlencode 'image=https://example.com/images/news-2101.jpg' \
  --data-urlencode 'type=article' \
  --data-urlencode 'title=Council climate plan' \
  --data-urlencode 'link=https://example.com/news/2101' \
  --data-urlencode 'taxonomy_keys=section,topic,author' \
  --data-urlencode 'taxonomy_titles=Section,Topic,Author' \
  --data-urlencode 'term_keys[section]=section-news' \
  --data-urlencode 'term_titles[section]=News' \
  --data-urlencode 'term_keys[topic]=topic-climate,topic-energy' \
  --data-urlencode 'term_titles[topic]=Climate,Energy' \
  --data-urlencode 'term_keys[author]=author-robin-lee' \
  --data-urlencode 'term_titles[author]=Robin Lee'

JavaScript

// Node.js 18+; built-in fetch. Plain HTTP data, not SDK arguments.
async function main() {
  const url = new URL("/api/v1/user/content-sync-and-check/news-2101", process.env.WALLKIT_API_BASE);
  const query = {
    "image": "https://example.com/images/news-2101.jpg",
    "type": "article",
    "title": "Council climate plan",
    "link": "https://example.com/news/2101",
    "taxonomy_keys": "section,topic,author",
    "taxonomy_titles": "Section,Topic,Author",
    "term_keys[section]": "section-news",
    "term_titles[section]": "News",
    "term_keys[topic]": "topic-climate,topic-energy",
    "term_titles[topic]": "Climate,Energy",
    "term_keys[author]": "author-robin-lee",
    "term_titles[author]": "Robin Lee"
};
  url.search = new URLSearchParams(query).toString();
  const response = await fetch(url, {
    headers: {resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN}
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library. Plain HTTP data, not SDK arguments.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urlencode, urljoin
from urllib.request import Request, urlopen

query = {
    "image": "https://example.com/images/news-2101.jpg",
    "type": "article",
    "title": "Council climate plan",
    "link": "https://example.com/news/2101",
    "taxonomy_keys": "section,topic,author",
    "taxonomy_titles": "Section,Topic,Author",
    "term_keys[section]": "section-news",
    "term_titles[section]": "News",
    "term_keys[topic]": "topic-climate,topic-energy",
    "term_titles[topic]": "Climate,Energy",
    "term_keys[author]": "author-robin-lee",
    "term_titles[author]": "Robin Lee"
}
url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content-sync-and-check/news-2101")
request = Request(url + "?" + urlencode(query), headers={
    "resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_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))

If saved successfully, Wallkit reuses or creates the three resource-scoped taxonomies and the listed resource-scoped terms, then links each taxonomy–term pair to this content. Multiple values in one dimension create multiple classification links. An existing content record skips this creation path; these fields do not update it. Read allow separately from classification and HTTP status.

Specialist report: subjects, regions and series

Assume this item is missing in the selected resource. Its human classification tree is:

Storage market report (report)
  Subject [subject]
    Storage [subject-storage]
    Grid [subject-grid]
  Region [region]
    Europe [region-europe]
    North America [region-north-america]
  Series [series]
    Market briefs [series-market-briefs]

Use this object as content for the initialized library’s new wk.content(content) constructor. Its taxonomy keys name dimensions; each label is a taxonomy title, and each items entry maps slug to a term key and name to its title.

{
  "id": "report-3101",
  "type": "report",
  "title": "Storage market report",
  "link": "https://example.com/reports/3101",
  "image": "https://example.com/images/report-3101.jpg",
  "taxonomies": {
    "subject": {
      "label": "Subject",
      "items": [
        {
          "slug": "subject-storage",
          "name": "Storage"
        },
        {
          "slug": "subject-grid",
          "name": "Grid"
        }
      ]
    },
    "region": {
      "label": "Region",
      "items": [
        {
          "slug": "region-europe",
          "name": "Europe"
        },
        {
          "slug": "region-north-america",
          "name": "North America"
        }
      ]
    },
    "series": {
      "label": "Series",
      "items": [
        {
          "slug": "series-market-briefs",
          "name": "Market briefs"
        }
      ]
    }
  }
}

The three requests below carry the same fields. The JavaScript query object is ordinary HTTP request data; each bracketed key names one CSV group in the REST query. An administrator could distinguish Market briefs from another series in a Plan rule, or use the Region classifications when designing an offer. Those are separately configured rules; this request only supplies classification.

cURL

curl -G "${WALLKIT_API_BASE}/api/v1/user/content-sync-and-check/report-3101" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}" \
  --data-urlencode 'image=https://example.com/images/report-3101.jpg' \
  --data-urlencode 'type=report' \
  --data-urlencode 'title=Storage market report' \
  --data-urlencode 'link=https://example.com/reports/3101' \
  --data-urlencode 'taxonomy_keys=subject,region,series' \
  --data-urlencode 'taxonomy_titles=Subject,Region,Series' \
  --data-urlencode 'term_keys[subject]=subject-storage,subject-grid' \
  --data-urlencode 'term_titles[subject]=Storage,Grid' \
  --data-urlencode 'term_keys[region]=region-europe,region-north-america' \
  --data-urlencode 'term_titles[region]=Europe,North America' \
  --data-urlencode 'term_keys[series]=series-market-briefs' \
  --data-urlencode 'term_titles[series]=Market briefs'

JavaScript

// Node.js 18+; built-in fetch. Plain HTTP data, not SDK arguments.
async function main() {
  const url = new URL("/api/v1/user/content-sync-and-check/report-3101", process.env.WALLKIT_API_BASE);
  const query = {
    "image": "https://example.com/images/report-3101.jpg",
    "type": "report",
    "title": "Storage market report",
    "link": "https://example.com/reports/3101",
    "taxonomy_keys": "subject,region,series",
    "taxonomy_titles": "Subject,Region,Series",
    "term_keys[subject]": "subject-storage,subject-grid",
    "term_titles[subject]": "Storage,Grid",
    "term_keys[region]": "region-europe,region-north-america",
    "term_titles[region]": "Europe,North America",
    "term_keys[series]": "series-market-briefs",
    "term_titles[series]": "Market briefs"
};
  url.search = new URLSearchParams(query).toString();
  const response = await fetch(url, {
    headers: {resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN}
  });
  console.log(response.status, await response.json());
}
main().catch(console.error);

Python

# Python 3; standard library. Plain HTTP data, not SDK arguments.
import json
import os
from urllib.error import HTTPError
from urllib.parse import urlencode, urljoin
from urllib.request import Request, urlopen

query = {
    "image": "https://example.com/images/report-3101.jpg",
    "type": "report",
    "title": "Storage market report",
    "link": "https://example.com/reports/3101",
    "taxonomy_keys": "subject,region,series",
    "taxonomy_titles": "Subject,Region,Series",
    "term_keys[subject]": "subject-storage,subject-grid",
    "term_titles[subject]": "Storage,Grid",
    "term_keys[region]": "region-europe,region-north-america",
    "term_titles[region]": "Europe,North America",
    "term_keys[series]": "series-market-briefs",
    "term_titles[series]": "Market briefs"
}
url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content-sync-and-check/report-3101")
request = Request(url + "?" + urlencode(query), headers={
    "resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_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))

If saved successfully, Wallkit reuses or creates the three resource-scoped taxonomies and the listed resource-scoped terms, then links each taxonomy–term pair to this content. Multiple values in one dimension create multiple classification links. An existing content record skips this creation path; these fields do not update it. Read allow separately from classification and HTTP status.

Taxonomy and term serialization details

Use taxonomy_keys=section,topic,author with taxonomy_titles=Section,Topic,Author. For Topic, send term_keys[topic]=topic-climate,topic-energy and term_titles[topic]=Climate,Energy. Query encoders produce bracket names such as term_keys%5Btopic%5D and encoded CSV commas such as %2C. After query decoding, the server receives a keyed group whose leaf value is a string. Do not send a JSON-encoded object or term_keys[topic][] arrays in place of those CSV strings.

Taxonomy keys identify dimensions; term keys identify reusable values across the entire resource, not just one taxonomy. A term key reused in two dimensions refers to the same term record, with a separate relationship for each dimension. Prefer stable lowercase keys that reflect your meaning, such as region-europe, and supply readable titles separately. Stored keys are lowercased, but query group lookup uses the supplied trimmed taxonomy key; use that exact key for both bracketed groups. Reusing a key does not rename its existing title.

Input situationSupported behavior / practical guidance
Several dimensions or several terms in one dimensionOne taxonomy CSV entry per dimension; one keyed CSV group per dimension. The loop has no fixed dimension/term count, but request-size, parser and performance limits still apply. This is not an unlimited-request guarantee.
No classification inputsContent can be created without classification. Supply complete groups when you need taxonomy-based rules.
Missing/non-string taxonomy title listClassification synchronization stops; do not omit the title list when sending taxonomy keys.
Missing/non-string term groupTerm links for that dimension are skipped; its taxonomy may already have been created. Supply both term-key and term-title groups.
Fewer titles than keysMissing title indexes fall back to their corresponding key. Blank titles are not this fallback; extra titles are ignored. Prefer complete positional pairs.
Repeated keys / relationshipsExisting resource keys and relationship pairs are looked up for reuse. Repeating values does not create a metadata update path or promise request-level idempotency.
Empty values or consecutive commasNo uniform empty-value rejection is established; model callbacks can generate keys. Use nonempty keys/titles and avoid empty CSV entries.
Long valuesA key over 128 characters or a title over 255 is skipped for that taxonomy/term. Other work can still proceed; an access response is not an attachment receipt.
A comma inside a key or titleUnsupported as one CSV value: URL decoding happens before splitting, so %2C still becomes a delimiter. Choose a comma-free label/key.

Classification and paywall rules

Publisher-defined dimensions can be section, topic, author, subject, region, series or another useful grouping. They are separate from the content type (article or report). Consistent keys let separately configured access rules refer to the same classifications over time. This request does not create or change Plan rules.

Consistent classification can also provide context when designing a predictive paywall. This endpoint documents classification and an access decision; it defines no predictive scoring/training contract or automatic rule creation.

Request

GET with no request body or request Content-Type requirement. Response media type is JSON. Permits guest access with the context described above. See credential transport.

NameLocationTypeRequirement / defaultMeaning and constraint
resourceheaderstringrequired contextPublic resource key; not the secret.
tokenheaderstringmember contextExisting member-session token.
sessionheaderstringguest instead of tokenStable guest-session identifier; resource alone is insufficient.
firebase-tokenheaderstringFirebase-enabled member contextConfigured Firebase ID token alongside the Wallkit token.
keypathstringyesUse a clean whitespace-free content key, URL-encoded as one segment. The initial existing-item lookup uses the supplied route key.
return_contentquerybooleanoptionalInclude data with the richer content/taxonomy record. Omitted by default.
typequerystringif content missingNonempty content-type key; missing type can be created.
titlequerystringif content missingNonempty content title.
linkquerystringif content missingNonempty content link; no URL-validation promise.
imagequerystringoptionalImage URL stored for newly created content.
taxonomy_keys / taxonomy_titlesquerycomma-separated stringsoptionalBoth CSV lists are required for classification. Keys drive positional pairing; absent title indexes fall back to keys, while blank titles remain blank. Keys over 128 characters or titles over 255 are skipped.
term_keys[taxonomy-key] / term_titles[taxonomy-key]querykeyed groups of comma-separated stringsoptionalUse one group per taxonomy key, with positionally paired term keys/titles. Both groups must be present for that dimension. Use PHP-style bracket query names, not a JSON string or nested term arrays.

New content is created with currency USD; content type, image, taxonomy and relation records may also be created. Cache invalidation and synchronization happen before the access decision; allow: false does not mean nothing was created. Existing records skip these creation fields. Do not use this call as a metadata-only GET.

Result

HTTP 200 JSON.

Field / projectionType / presenceMeaning
top-level decisionaccess decisionUse the field definitions. Allow can be false after new content is created.

Alternate result

For a member without qualifying access, the newly created article-2001 remains registered even though access is denied. Withhold the content; use metadata/details to inspect state rather than repeating creation.

Response excerpt:

{
  "allow": false,
  "reason": "user_access",
  "message": "Content is not available for member subscription",
  "content": {
    "key": "article-2001",
    "title": "A guide to drum machines",
    "link": "https://example.com/articles/2001"
  },
  "guest_access": false
}

Recovery

HTTP statusAPI code / responseCauseNext action
401auth_failedRequired identity is missing.Supply the existing member token and check its selected resource context. For a permitted guest branch, supply a stable guest session instead of a member token.
401auth_access_failInactive account or locked/suspended resource relationship.Ask the administrator to check account activation and the resource relationship’s lock/suspension state.
401 / 403accessRole does not permit the operation.Use an identity permitted for this action; ask the administrator to check the assigned role.
404resource_not_existsRequired resource is missing or unknown.Check the resource public key and selected integration context with the administrator.
404incorrect_content_keyEmpty key or missing content in the resource.Check the publisher-provided content key in this resource. Use sync-and-check only for intended, enabled creation.
404incorrect_resource_keyInvalid context reaches the action.Check the resource public key and selected integration context with the administrator.
404user_access_denyInactive account or locked/suspended resource relationship.Ask the administrator to resolve the inactive account or locked/suspended resource relationship. Changing a Pricing does not repair this account restriction.
404method_not_allowedResource has not enabled sync-and-check.Ask the resource administrator whether content_sync_and_check is enabled; do not retry a disabled feature.
404incorrect_typeRequired creation field missing/empty when content does not exist.Supply a nonempty content-type key.
404incorrect_titleRequired creation field missing/empty when content does not exist.Supply a nonempty title for the missing item.
404incorrect_content_linkRequired creation field missing/empty when content does not exist.Supply a nonempty content link for the missing item.
409sync_content_errorException during synchronization; transaction rolled back.Check the creation values and contact support with req_guid. Inspect the item through metadata before deciding whether another write is appropriate; do not blindly retry.

See visitor and content context for applicable shared checks; follow the local error table above.

Next task

Read access details when you need to explain limits for the newly registered item; do not repeat sync merely to fetch metadata.

Read resource access details

GET /api/v1/user/content-access-details

Describe current member/guest plan access limits and usage for the selected resource. This is an explanatory limit summary, not an allow decision and not a new recorded content view.

Before you call

Use a member token or stable guest session; the resource key alone is insufficient. Guest/IP-plan details depend on configured guest/IP access. This resource-wide read does not require a content key. See visitor and content context for supplied configuration and shared checks.

Request

GET with no request body or request Content-Type requirement. Response media type is JSON. Permits guest access with the context described above. See credential transport.

NameLocationTypeRequirement / defaultMeaning and constraint
resourceheaderstringrequired contextPublic resource key; not the secret.
tokenheaderstringmember contextExisting member-session token.
sessionheaderstringguest instead of tokenStable guest-session identifier; resource alone is insufficient.
firebase-tokenheaderstringFirebase-enabled member contextConfigured Firebase ID token alongside the Wallkit token.

Use a token or guest session to establish the context. No explicit member-only guard applies here; guest plans can be summarized. No path/query filter, sort or pagination is read. It may populate caches, but does not add an allowed-view record.

Result

HTTP 200 JSON.

Field / projectionType / presenceMeaning
top-level objectresource-wide access detailsUse the field definitions. No is_content_viewed field in this projection.

Example: read resource access details

The member has a full-access Reader plan. Empty groups do not mean a denial when is_full_access is true. Supply the sample configuration and runtimes. These three requests are equivalent.

cURL

curl "${WALLKIT_API_BASE}/api/v1/user/content-access-details" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch. Supply existing integration configuration.
async function main() {
  const url = new URL("/api/v1/user/content-access-details", process.env.WALLKIT_API_BASE);
  const headers = { resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN };
  const response = await fetch(url, { method: "GET", headers });
  const body = await response.json();
  console.log(response.status, body);
}
main().catch(console.error);

Python

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

url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content-access-details")
headers = {"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}
request = Request(url, headers=headers, 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:

{
  "guest_access": false,
  "plan_title": "Reader plan",
  "is_full_access": true,
  "content_types": {
    "items": [],
    "accessLimit": 0,
    "usedLimitInPeriod": 0,
    "accessLimitPeriod": "",
    "exclude": false
  },
  "content_terms": {
    "items": [],
    "accessLimit": 0,
    "usedLimitInPeriod": 0,
    "accessLimitPeriod": "",
    "exclude": false
  }
}

Alternate result

This configured plan permits five article views in its 1 month counting period and reports two already used. The period is a configured label, not a universal enum or fixed duration. Explain this usage in the UI; make the actual content check before serving an item. Other limit groups are omitted from this excerpt.

HTTP 200 response excerpt:

{
  "guest_access": false,
  "plan_title": "Limited reader",
  "is_full_access": false,
  "content_types": {
    "items": [
      {
        "key": "article",
        "accessLimit": 5,
        "usedLimitInPeriod": 2,
        "accessLimitPeriod": "1 month",
        "exclude": false
      }
    ],
    "accessLimit": 5,
    "usedLimitInPeriod": 2,
    "accessLimitPeriod": "1 month",
    "exclude": false
  }
}

Recovery

HTTP statusAPI code / responseCauseNext action
404resource_not_existsRequired resource is missing or unknown.Check the resource public key and selected integration context with the administrator.
404incorrect_resource_keyInvalid resource context reaches validation.Check the resource public key and selected integration context with the administrator.
404user_access_denyInactive account or locked/suspended resource relationship.Ask the administrator to resolve the inactive account or locked/suspended resource relationship. Changing a Pricing does not repair this account restriction.

See visitor and content context for applicable shared checks; follow the local error table above.

Next task

Check a specific item when your application needs an allow/deny decision rather than a limits summary.

Read access details for content

GET /api/v1/user/content-access-details/{key}

Describe plan type/term limits relevant to one existing content item and report whether it has previously been viewed with allowed access. This does not run or record a new access check.

Before you call

Use an existing member token or resolved guest-session context and a content key from your publishing integration or metadata. The item must exist in the selected resource; a resource-only request is insufficient. See visitor and content context for supplied configuration and shared checks.

Request

GET with no request body or request Content-Type requirement. Response media type is JSON. Permits guest access with the context described above. See credential transport.

NameLocationTypeRequirement / defaultMeaning and constraint
resourceheaderstringrequired contextPublic resource key; not the secret.
tokenheaderstringmember contextExisting member-session token.
sessionheaderstringguest instead of tokenStable guest-session identifier; resource alone is insufficient.
firebase-tokenheaderstringFirebase-enabled member contextConfigured Firebase ID token alongside the Wallkit token.
keypathstringyesTrimmed content key within the selected resource.

Unlike the resource-wide details call, this action explicitly requires an active user context. A resolved guest-session user object can satisfy that guard; a resource-only request cannot. Type limits are narrowed to the content’s type and term limits to its relationships. No query filtering/pagination is read.

Result

HTTP 200 JSON.

Field / projectionType / presenceMeaning
top-level objectper-content access detailsUse the field definitions, including is_content_viewed. This does not record a new allowed view.

Example: read access details for content

Read article-1001’s prior-view flag for a full-access member. The result explains state without recording another view. Supply the sample configuration and runtimes. These three requests are equivalent.

cURL

curl "${WALLKIT_API_BASE}/api/v1/user/content-access-details/article-1001" \
  -H "resource: ${RESOURCE_KEY}" \
  -H "token: ${USER_TOKEN}"

JavaScript

// Node.js 18+; built-in fetch. Supply existing integration configuration.
async function main() {
  const url = new URL("/api/v1/user/content-access-details/article-1001", process.env.WALLKIT_API_BASE);
  const headers = { resource: process.env.RESOURCE_KEY, token: process.env.USER_TOKEN };
  const response = await fetch(url, { method: "GET", headers });
  const body = await response.json();
  console.log(response.status, body);
}
main().catch(console.error);

Python

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

url = urljoin(os.environ["WALLKIT_API_BASE"], "/api/v1/user/content-access-details/article-1001")
headers = {"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}
request = Request(url, headers=headers, 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:

{
  "guest_access": false,
  "plan_title": "Reader plan",
  "is_full_access": true,
  "content_types": {
    "items": [],
    "accessLimit": 0,
    "usedLimitInPeriod": 0,
    "accessLimitPeriod": "",
    "exclude": false
  },
  "content_terms": {
    "items": [],
    "accessLimit": 0,
    "usedLimitInPeriod": 0,
    "accessLimitPeriod": "",
    "exclude": false
  },
  "is_content_viewed": true
}

Alternate result

No prior allowed-view record is represented for article-1001. This flag alone does not decide access; make the content access check for a serving decision.

Response excerpt:

{
  "is_content_viewed": false
}

Recovery

HTTP statusAPI code / responseCauseNext action
401auth_failedRequired identity is missing.Supply the existing member token and check its selected resource context. For a permitted guest branch, supply a stable guest session instead of a member token.
401auth_access_failInactive account or locked/suspended resource relationship.Ask the administrator to check account activation and the resource relationship’s lock/suspension state.
401 / 403accessRole does not permit the operation.Use an identity permitted for this action; ask the administrator to check the assigned role.
404resource_not_existsRequired resource is missing or unknown.Check the resource public key and selected integration context with the administrator.
404incorrect_content_keyEmpty key or missing content in the resource.Check the publisher-provided content key in this resource. Use sync-and-check only for intended, enabled creation.
404incorrect_resource_keyInvalid context reaches the action.Check the resource public key and selected integration context with the administrator.
404user_access_denyInactive account or locked/suspended resource relationship.Ask the administrator to resolve the inactive account or locked/suspended resource relationship. Changing a Pricing does not repair this account restriction.

See visitor and content context for applicable shared checks; follow the local error table above.

Next task

Check current access when deciding to serve the item; prior-view information is not the complete access decision.

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