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
| Operation | Method / path |
|---|---|
| List content linked to the user | GET /api/v1/user/content |
| Check content access | GET /api/v1/user/content/{key} |
| Sync missing content and check access | GET /api/v1/user/content-sync-and-check/{key} |
| Read resource access details | GET /api/v1/user/content-access-details |
| Read access details for content | GET /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.
| Name | Location | Type | Requirement / default | Meaning and constraint |
|---|---|---|---|---|
resource | header | string | required context | Public resource key; not the secret. |
token | header | string | member context | Existing member-session token. |
firebase-token | header | string | Firebase-enabled member context | Configured Firebase ID token alongside the Wallkit token. |
page | query | integer | optional | Use 1 for the first page; pass it explicitly. No explicit omitted-page default in the operation. |
limit | query | integer | optional | Default 10. No universal maximum is specified. |
filter | query | object / JSON string | optional | Model-field filters; bracket notation or JSON string. Text fields use case-insensitive substring matching; numeric arrays use membership. |
order / by | query | string | optional | order takes precedence over by; default indicated below. |
sort | query | string | optional | Default 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 / projection | Type / presence | Meaning |
|---|---|---|
items[] | content with term arrays | Use 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 status | API code / response | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed | Required identity is missing. | Supply the existing member token and check its selected resource context. |
| 401 | auth_access_fail | Inactive account or locked/suspended resource relationship. | Ask the administrator to check account activation and the resource relationship’s lock/suspension state. |
| 401 / 403 | access | Role does not permit the operation. | Use an identity permitted for this action; ask the administrator to check the assigned role. |
| 404 | resource_not_exists | Required 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.
| Name | Location | Type | Requirement / default | Meaning and constraint |
|---|---|---|---|---|
resource | header | string | required | Resource public key selecting the item and identity context; never the resource secret. |
token | header | string | member branch | Existing Wallkit member-session token. |
session | header | string | guest branch instead of token | Stable guest-session identifier; a resource key alone does not establish this context. |
firebase-token | header | string | Firebase-enabled member context | Send the configured Firebase ID token alongside token; do not add it to unrelated guest requests. |
key | path | string | required | Resource-scoped content key; trimmed and encoded as one path segment. |
return_content | query | boolean | optional; omitted/false adds no data | True 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 status | Code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed | No 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. |
| 401 | auth_access_fail | Global 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 / 403 | access | Context 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. |
| 404 | resource_not_exists / incorrect_resource_key | Resource public key does not resolve. | Check the supplied resource public key and integration context; replacing a user token does not repair the resource key. |
| 404 | incorrect_content_key | Empty 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. |
| 404 | user_access_deny | Account/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 situation | Supported behavior / practical guidance |
|---|---|
| Several dimensions or several terms in one dimension | One 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 inputs | Content can be created without classification. Supply complete groups when you need taxonomy-based rules. |
| Missing/non-string taxonomy title list | Classification synchronization stops; do not omit the title list when sending taxonomy keys. |
| Missing/non-string term group | Term links for that dimension are skipped; its taxonomy may already have been created. Supply both term-key and term-title groups. |
| Fewer titles than keys | Missing title indexes fall back to their corresponding key. Blank titles are not this fallback; extra titles are ignored. Prefer complete positional pairs. |
| Repeated keys / relationships | Existing 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 commas | No uniform empty-value rejection is established; model callbacks can generate keys. Use nonempty keys/titles and avoid empty CSV entries. |
| Long values | A 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 title | Unsupported 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.
| Name | Location | Type | Requirement / default | Meaning and constraint |
|---|---|---|---|---|
resource | header | string | required context | Public resource key; not the secret. |
token | header | string | member context | Existing member-session token. |
session | header | string | guest instead of token | Stable guest-session identifier; resource alone is insufficient. |
firebase-token | header | string | Firebase-enabled member context | Configured Firebase ID token alongside the Wallkit token. |
key | path | string | yes | Use a clean whitespace-free content key, URL-encoded as one segment. The initial existing-item lookup uses the supplied route key. |
return_content | query | boolean | optional | Include data with the richer content/taxonomy record. Omitted by default. |
type | query | string | if content missing | Nonempty content-type key; missing type can be created. |
title | query | string | if content missing | Nonempty content title. |
link | query | string | if content missing | Nonempty content link; no URL-validation promise. |
image | query | string | optional | Image URL stored for newly created content. |
taxonomy_keys / taxonomy_titles | query | comma-separated strings | optional | Both 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] | query | keyed groups of comma-separated strings | optional | Use 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 / projection | Type / presence | Meaning |
|---|---|---|
| top-level decision | access decision | Use 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 status | API code / response | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed | Required 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. |
| 401 | auth_access_fail | Inactive account or locked/suspended resource relationship. | Ask the administrator to check account activation and the resource relationship’s lock/suspension state. |
| 401 / 403 | access | Role does not permit the operation. | Use an identity permitted for this action; ask the administrator to check the assigned role. |
| 404 | resource_not_exists | Required resource is missing or unknown. | Check the resource public key and selected integration context with the administrator. |
| 404 | incorrect_content_key | Empty 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. |
| 404 | incorrect_resource_key | Invalid context reaches the action. | Check the resource public key and selected integration context with the administrator. |
| 404 | user_access_deny | Inactive 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. |
| 404 | method_not_allowed | Resource has not enabled sync-and-check. | Ask the resource administrator whether content_sync_and_check is enabled; do not retry a disabled feature. |
| 404 | incorrect_type | Required creation field missing/empty when content does not exist. | Supply a nonempty content-type key. |
| 404 | incorrect_title | Required creation field missing/empty when content does not exist. | Supply a nonempty title for the missing item. |
| 404 | incorrect_content_link | Required creation field missing/empty when content does not exist. | Supply a nonempty content link for the missing item. |
| 409 | sync_content_error | Exception 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.
| Name | Location | Type | Requirement / default | Meaning and constraint |
|---|---|---|---|---|
resource | header | string | required context | Public resource key; not the secret. |
token | header | string | member context | Existing member-session token. |
session | header | string | guest instead of token | Stable guest-session identifier; resource alone is insufficient. |
firebase-token | header | string | Firebase-enabled member context | Configured 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 / projection | Type / presence | Meaning |
|---|---|---|
| top-level object | resource-wide access details | Use 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 status | API code / response | Cause | Next action |
|---|---|---|---|
| 404 | resource_not_exists | Required resource is missing or unknown. | Check the resource public key and selected integration context with the administrator. |
| 404 | incorrect_resource_key | Invalid resource context reaches validation. | Check the resource public key and selected integration context with the administrator. |
| 404 | user_access_deny | Inactive 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.
| Name | Location | Type | Requirement / default | Meaning and constraint |
|---|---|---|---|---|
resource | header | string | required context | Public resource key; not the secret. |
token | header | string | member context | Existing member-session token. |
session | header | string | guest instead of token | Stable guest-session identifier; resource alone is insufficient. |
firebase-token | header | string | Firebase-enabled member context | Configured Firebase ID token alongside the Wallkit token. |
key | path | string | yes | Trimmed 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 / projection | Type / presence | Meaning |
|---|---|---|
| top-level object | per-content access details | Use 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 status | API code / response | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed | Required 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. |
| 401 | auth_access_fail | Inactive account or locked/suspended resource relationship. | Ask the administrator to check account activation and the resource relationship’s lock/suspension state. |
| 401 / 403 | access | Role does not permit the operation. | Use an identity permitted for this action; ask the administrator to check the assigned role. |
| 404 | resource_not_exists | Required resource is missing or unknown. | Check the resource public key and selected integration context with the administrator. |
| 404 | incorrect_content_key | Empty 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. |
| 404 | incorrect_resource_key | Invalid context reaches the action. | Check the resource public key and selected integration context with the administrator. |
| 404 | user_access_deny | Inactive 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.