Vet a creator
Check a profile against safety criteria before you work with them.
Vetting reads a creator profile and the posts behind it, then reports against the safety criteria you asked for. You hand it a profile link, not an id, so it works for a creator Zelf has never seen.
The shape is the same as an AI analysis: create, poll, download. The difference is the input, a profile, and the criteria you choose.
Every step shows the same call in all six Zelf clients and in cURL. Pick a language and the install line above the snippet switches with it. Each client reads your key from ZELF_API_KEY.
- 01Choose the safety checks
- 02Start the vetting
- 03Wait for it to finish
- 04Download the report
- 05Read the disclosure findings
- 06See the posts it read
Choose the safety checks
Choose predefined safety criteria, describe your own, or add DISCLOSURE to detect stated commercial relationships. Disclosures are reported separately, not as violations. Only type is required on each entry.
ADULT_CONTENTpredefined | Sexual or adult material. |
|---|---|
ARMS_AND_AMMUNITIONpredefined | Weapons and ammunition. |
MISINFORMATIONpredefined | Claims presented as fact that are not. |
PROFANITYpredefined | Strong or offensive language. |
POLITICSpredefined | Political positions and campaigning. |
DISCLOSUREoptional detection | Look for stated sponsorships, affiliate commissions, gifts, offer terms and risk warnings. Findings go in disclosures, not violations. |
USER_DEFINED_SAFETY_CRITERIONyours | Put the criterion in extra, written as you would explain it to a person. extra is ignored on the predefined types. |
Start the vetting
content_scope decides which of the creator posts feed the analysis. Omitted, it means their own posts only. Set resync_profile when you want a fresh crawl of a creator Zelf already holds.
profile_urlstring, required | Direct link to the profile on instagram or tiktok. |
|---|---|
content_scope.ownedboolean | Posts the creator published. Defaults to true. |
content_scope.repostboolean | Posts they reposted. Defaults to false. |
content_scope.likedboolean | Posts they liked. Defaults to false. |
resync_profileboolean | Re-index the creator even if Zelf already holds them. Defaults to false. When it triggers a crawl the response carries sync_job_id. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.creator_vetting.create(
profile_url="https://www.instagram.com/milesandmornings",
resync_profile=False,
content_scope={
"owned": True,
"repost": True,
"liked": False,
},
safety_checks=[{
"type": "ADULT_CONTENT",
}, {
"type": "PROFANITY",
}, {
"type": "POLITICS",
}, {
"type": "DISCLOSURE",
}, {
"type": "USER_DEFINED_SAFETY_CRITERION",
"extra": "Promotes competing running shoe brands",
}],
)
print(result){
"results": [
{
"id": "bod_5Rn1Wf8d",
"creator_id": "cr_4Tz9Hs1k",
"type": "CREATOR_ANALYSIS",
"status": "IN_PROGRESS",
"created_at": "2026-10-07T09:31:44Z",
"updated_at": "2026-10-07T09:31:44Z",
"creator": {
"id": "cr_4Tz9Hs1k",
"username": "milesandmornings",
"name": "Miles and Mornings",
"platform": "instagram",
"platform_url": "https://www.instagram.com/milesandmornings",
"follower_count": 128400,
"active_content_count": 612
},
"sync_job_id": null,
"analysis": [
{ "id": "ban_9Ld3Kp2m", "scope": "CONTENT", "status": "IN_PROGRESS" }
],
"safety_checks": [
{ "type": "ADULT_CONTENT" },
{ "type": "PROFANITY" },
{ "type": "POLITICS" },
{ "type": "DISCLOSURE" },
{
"type": "USER_DEFINED_SAFETY_CRITERION",
"extra": "Promotes competing running shoe brands"
}
],
"content_scope": { "owned": true, "repost": true, "liked": false }
}
],
"item_count": 1,
"page": 1,
"next": null,
"previous": null
}Vetting analyzes up to 250 posts in total under the current content policy. The stored posts can include historical content no longer visible on the profile. A run can take an hour or more while waiting for preprocessing.
Wait for it to finish
Read the vetting back by id. The statuses are the same four as an AI analysis: IN_PROGRESS, then COMPLETED, EMPTY_RESULT or FAILED. Poll every minute or two, or use the completion webhook to avoid polling.
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.creator_vetting.list(
id="bod_5Rn1Wf8d",
)
print(result)The creator_vetting.completed webhook replaces this loop. It is a notification, so you still fetch the result in the next step.
Download the report
The findings against each criterion live in the downloaded payload, not in the API response. The response carries a signed link that expires, so fetch it rather than storing it.
vetting_idstring | Every run under the vetting. Send this or id, not both. |
|---|---|
idstring | One run. |
resultobject or null | download_url, expires_at in unix seconds, and format. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.creator_vetting.results(
vetting_id="bod_5Rn1Wf8d",
)
print(result)Read the disclosure findings
When safety_checks includes DISCLOSURE, the downloaded report includes disclosures alongside the normal summary, themes and violations. Each list entry is one detected disclosure, with its post id, original URL and source. This illustrative excerpt shows the format, not a live verdict.
content_idstring | Existing con_... post identifier for matching against the contents endpoint. |
|---|---|
content_urlstring | Original post URL, or an empty string if unavailable. |
sourcestring | frame, audio, title, caption or metadata. frame means on-screen text; audio means speech transcription; caption means the published post text. |
kindstring | sponsorship, affiliate, gifted, relationship, terms or risk. A disclosure is not a violation. |
textstring | The detected disclosure quote. |
time_secondsnumber or null | Approximate location in the video, when known. Published post text normally has no video time. |
{
"disclosures": [
{
"content_id": "con_7Km3Qx9pLt",
"content_url": "https://www.instagram.com/p/EXAMPLE/",
"source": "frame",
"kind": "sponsorship",
"text": "Paid partnership with Example",
"time_seconds": 5
}
]
}If no disclosure is detected, disclosures is []. Without the DISCLOSURE check, the field is omitted. There is no result wrapper, channels or coverage in the new list. Videos analyzed previously are not re-analyzed for disclosure, and resync_profile does not force a new analysis. Video checks are limited to 10 minutes and sampled frames can miss brief text. An empty list is not proof that every source was checked or that a post has no disclosure. Existing saved reports keep their earlier format. Findings are not a legal compliance assessment.
See the posts it read
Same pair as an AI analysis, keyed by vetting_id. Use the statistics call when you only need the count and the totals.
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.creator_vetting.contents(
vetting_id="bod_5Rn1Wf8d",
page=1,
)
print(result)Summary
| Step | Request | Input | Output |
|---|---|---|---|
| 1 | POST /api/v1/creator/vetting/ | profile_url, content_scope, safety_checks | vetting id (bod_...) |
| 2 | GET /api/v1/creator/vetting/?id= | vetting id | status |
| 3 | GET /api/v1/creator/vetting/results/ | vetting id | signed download_url |
| 4 | GET /api/v1/creator/vetting/contents/ | vetting id | the posts it read |