One request does everything: the first import, a scheduled reconcile and a push after one change.
It is idempotent on external_id.
POST /api/v1/integration/entitlements
Authorization: Bearer wik_...
Content-Type: application/json
{ "records": [ { ... }, { ... } ] }At most 500 records in a request. Send requests one after another. The body is records and
nothing else.
A full record
{
"external_id": "member-1001",
"status": "active",
"normalized_state": "active",
"tier": "commercial",
"capabilities": ["physical_sales"],
"effective_from": "2025-12-03T01:02:03Z",
"expires_at": null,
"credential": {
"title": "Commercial license",
"holder_name": "Example Print Shop",
"registration_id": "EX-1A67D8F2",
"registered_at": "2025-12-03T01:02:03Z",
"publish_terms": false,
"retired": false
},
"identities": [
{ "source": "website", "url": "https://shop.example.com", "public": true },
{ "source": "yourshop_storefront", "handle": "example", "url": "https://yourshop.example/s/example", "public": true, "assurance": "issuer_hosted" }
]
}external_id, status and normalized_state are required. Everything else is optional.
State
status is your own wording, up to 200 characters. Warden stores it and never interprets it. Use a
short, coarse word such as active, lapsed, revoked or withdrawn. Do not put a customer
identifier, a payment provider's state or a staff note in it. Warden never shows status outside
your organization, and it is not quoted in a message to a seller.
normalized_state is active, inactive or unknown, and you decide it. Warden will not work it
out from status, because a word like "approved" means different things in different systems.
Define active as whatever your own verification page would call valid at the moment you send the
record.
tier is your own label, up to 200 characters, stored and not interpreted.
effective_from and expires_at are ISO 8601 dates. Warden stores them as instants in UTC.
Capabilities
capabilities is what the membership grants, from a closed list. Today the list has one value:
| Value | Meaning |
|---|---|
physical_sales | The member may sell physical prints of the creator's designs. |
Send what the membership grants whether or not it is currently in force. State and capability are separate facts, and Warden never derives one from the other. A value outside the list is refused. If your program grants something the list does not cover, ask for it to be added, and until then leave it out.
Credential
credential creates the public verification page. A record sent without one has an entitlement and
no public page. Leaving credential out of a later sync changes nothing, and the page stays. To
withdraw it, send retired: true, or erase the record.
| Field | |
|---|---|
holder_name | Required inside a credential, up to 200 characters. Printed publicly, so use a business or trading name. A value that is entirely an email address is refused. A trading name that is a handle, such as @SomePrintShop, is a name and passes. |
title | Up to 80 characters. "Commercial license" when you leave it out. |
registration_id | Your own printed reference, up to 64 characters. Warden displays it and never looks anything up by it. |
registered_at | An ISO 8601 date. |
publish_terms | Accepted and stored. The public page draws nothing from it today, because a creator's rules beside a holder's name read as a judgment about that holder. False when left out. |
retired | Withdraws the credential itself. The page keeps resolving and says it was withdrawn. For a member who lapsed, send normalized_state: inactive and leave retired false. False when left out. |
Identities
identities is where the member sells, at most 50 in a record. It is the full set your system
vouches for.
| You send | What happens to the identities you supplied before |
|---|---|
| A list | Each one in the list is written. Anything you sent before and now leave out is withdrawn. |
[] | Every identity you supplied is withdrawn. |
No identities key | Nothing changes. |
A sync never touches identities an admin typed into Warden by hand, or identities a member added through an enrollment invitation. To remove those as well, for a member who deleted their account, erase the record.
The verification page lists public identities only while the record reads active. A lapsed, withdrawn or unconfirmed member's shop links stay stored and are not shown. They come back when a sync says active again.
| Field | |
|---|---|
source | Required. The platform, up to 80 characters. Warden lowercases it. |
url | Up to 600 characters, and it must be http or https. |
handle | Up to 200 characters. Optional when there is a url. |
public | Whether the verification page lists it. False when left out. |
assurance | registered, the default, for anything the member typed into a form. issuer_hosted only for a storefront the creator's own platform hosts and controls. |
An identity needs a handle or a url. Two identities in one record that come to the same thing
are stored once.
When you send a url and no handle, Warden reads the handle from a shop's own page, for two
sources only: etsy, from an address such as etsy.com/shop/<name> or <name>.etsy.com, and
ebay, from ebay.<tld>/usr/<name> or /str/<name>. Otherwise it leaves the handle empty, and the
URL is still stored and shown. Only an identity with a handle, or an address Warden can compare,
can link an investigation to the member.
No verified value exists for assurance, because Warden does not check who controls an account.
What is refused
Unknown fields are refused by name, on the body, on a record, on credential and on an identity.
Warden has no field for an email address, a phone number, a street address, a tax id, a payment
detail or a provider account id, and it will not quietly accept one.
{ "index": 3, "external_id": "member-1004", "refused": "record", "because": "warden does not accept email on a record. It accepts: external_id, status, normalized_state, tier, capabilities, effective_from, expires_at, credential, identities." }One bad identity
An identity Warden cannot store is refused by itself. The record's state, tier, capabilities, dates and credential are still written, so a lapse always lands even when a shop address in the same record is one Warden refuses. The refusal names the record and the identity by position:
{ "index": 7, "external_id": "member-1008", "refused": "identity", "identity": 1, "because": "identity.url has to be an http or https address." }A refused identity may have been meant to replace one you sent earlier, and Warden cannot always
tell which. So the identities you supplied before on the same source stay as they are until a
sync sends a set Warden can read. Identities on other sources follow the list as usual. When a
refused identity has no readable source, nothing on that record is withdrawn in that request.
If identities itself cannot be read, because it is not a list or has more than 50 entries, the
record is written and its stored identities are left alone, with "refused": "identities".
The response
{
"records": [
{
"external_id": "member-1001",
"public_id": "q3Zr8mW1x0aB4cD5eF6gHi",
"verify_url": "https://3dwarden.com/verify/q3Zr8mW1x0aB4cD5eF6gHi",
"normalized_state": "active",
"capabilities": ["physical_sales"],
"last_synced_at": "2026-09-21T18:04:11.123456+00:00",
"retired": false
}
],
"rejected": [
{ "index": 3, "external_id": "member-1004", "refused": "record", "because": "credential.holder_name is shown publicly and may not be an email address." }
]
}A request with a valid key and a readable body returns 200. A bad record is refused by itself and
nothing of it is stored. The others are written. refused says how much was refused: record,
identity or identities. Only record means the record was not written.
records holds the records that were written, in the order you sent them. A refused record is
absent, so positions shift. Match on external_id.
index is the record's position in your request, whether the API or the database refused it.
external_id is present unless the record had none. Log rejected and carry on.
public_id is null for a record with no credential. When there is one, store it against your
member. It is minted once and never changes while Warden holds the record, through lapses,
reactivations and withdrawals.
See Sync entitlements for the reference, and Errors for what a whole request can be refused with.
Plans
The API is part of the Creator and Studio plans. For an organization on a plan without it, a sync
still updates the records Warden already holds. A new record comes back in rejected with
"refused": "record". A new credential is not minted, so that record's public_id stays null.