Guides
Vision Link: Photo Understanding and Visual Intelligence
What a Persona sees in a photo, what she does with it, and what you must do when a photo is problematic.
Overview
Vision Link lets a Persona see photos. Three things become possible:
- She recognizes herself in a photo.
- She recognizes the User.
- She understands and reacts to any photo shared in the conversation.
Vision is off by default. You turn it on in each reply request, and you choose which of the photos below we analyze.
Videos work the same way, with their own options and their own price. See Vision Link: Video.
Availability
Chat Model 2.0 and above. See Model Versions.
What Gets Analyzed
A Persona can be given access to four kinds of photos: the User's profile picture, her own profile picture, the photos the User sends her, and the photos she is allowed to send. Each one is a separate option on replyParameters.vision.
| Option | What it analyzes |
|---|---|
userProfilePictureAnalysis | The User's main profile picture |
personaProfilePictureAnalysis | The Persona's main profile picture |
sharedPicturesAnalysis | The photos the User sends during the conversation |
shareablePicturesAnalysis | The photos the Persona can send, so she knows what she is sharing. See Smart Share |
Switch all four on. Each one adds context the Persona is otherwise missing.
Two rules to know before you report a bug:
- Recognizing a face takes two options.
sharedPicturesAnalysison its own lets the Persona describe a photo. To know whether the person in it is the User, she also needsuserProfilePictureAnalysis. Same for her own face: addpersonaProfilePictureAnalysis. - Only the main profile picture is used. Photo albums are invisible to the Persona. She does not know an album exists, and she does not know what is in it, for the User or for herself. Album awareness is planned for a future release.
Problematic Photos: Who Does What
Every photo a User sends is analyzed, and problematic content is reported to you. Read this section carefully. Two things catch integrators out: you have to ask for the report, and the report blocks nothing.
What we do
- We analyze the photo.
- If the analysis flags it as violent, self-harm or illegal content, we send you a
chat.image.rejectedevent on your Incoming Content Endpoint.
There are three reasons, and no others:
| Reason | Meaning |
|---|---|
VIOLENT_CONTENT | Violence, or graphic and unsafe violent material. |
SELF_HARM | Self-harm, suicidal content, or related imagery. |
ILLEGAL_CONTENT | Illegal or illicit content prohibited by policy or law. |
Nudity and explicit adult photos are never flagged. Moderation here means violence, self-harm and illegal content, nothing else.
What the event does not do
The event is a notification. That is its entire job. Concretely, none of this happens:
- No ban. There is no
conversationStopReasonand noconversationStoppedUntilon the answer. Ask us for a reply again and we answer as usual. - Nothing is withheld from the Persona. She reacts to a flagged photo the same way she reacts to any other.
- Nothing happens to the photo or the User. We do not remove the photo, hide it from your platform, or act on the User in any way.
Core Guardian is the one that behaves differently, and the two are easy to mix up. Core Guardian reads the User's text, and it really does stop conversations: it returns a stop reason and a ban duration on the answer. Photo analysis never does that.
Photos you provide are not filtered either
shareablePictures is your own catalogue, and we do not police it. If our analysis flags one of your photos, the Persona can still pick it and send it to a User. The one exception is a forced share: forcePictureSharing never sends a flagged photo.
Vet your catalogue on your side. Here too videos differ: a flagged shareable video is never sent, forced or not.
What you must do
Because nothing is blocked on our side, your own moderation is the only thing that can act on a problematic photo. Stopping a User is your decision, and your job.
What we recommend:
- Receive the event on your incoming content endpoint.
- Block the conversation temporarily, if you deem it necessary. Stop asking us for replies on that
userProfileIdandpersonaProfileIdpair. We only write a reply when you ask for one, so not asking is the block. There is nothing to call on our side. - Send the photo to your own moderation service. The event gives you the photo (
pictureUrl) and the message it came from (sourceProfileId). We never keep the image, so the review happens on your side, under your own rules and your own legal obligations. - Apply their decision. Warn, suspend, ban or report the User on your side. If the photo turns out to be fine, start asking us for replies again.
Pricing
Base cost: €0.015 per analyzed photo
You pay once per photo URL, and that one payment covers every conversation and every project of yours that uses the same URL.
- How long we keep an analysis depends on the
Cache-Controlheader your server returns with the photo, between one hour and one week. - When it expires, we check before charging again. We fetch the photo and compare it with the one we analyzed. Same photo: nothing to pay, and we keep it for another period. Different photo behind the same URL: fresh analysis, and you pay again.
- A flagged photo costs the same as any other. The analysis is billed first, the moderation check happens after.
- A photo we cannot fetch is free. No analysis, no charge, no event.
Profile pictures and reused photos therefore cost almost nothing over time.
Technical Implementation
Step 1: Use a V2 Model
Send a Chat Model 2.0 or above through replyParameters.chatModel, for example chat-2.1-pro.
Step 2: Enable Vision
Switch on the photo types you need in replyParameters.vision:
{
"userProfileId": "xxxx",
"personaProfileId": "yyyy",
"replyParameters": {
"chatModel": "chat-2.1-pro",
"vision": {
"userProfilePictureAnalysis": true,
"personaProfilePictureAnalysis": true,
"sharedPicturesAnalysis": true,
"shareablePicturesAnalysis": true
}
}
}Vision settings are read on every request, and any option you leave out counts as false. Send them on every call.
Step 3: Return Photo URLs in Your Chat History
In the Chat History Endpoint you host, add a pictureUrls array to each message, holding the URLs of the photos attached to that message. A message can carry text, photos, videos, or any combination.
{
"messages": [
{
"id": "msg_1042",
"sender": "USER",
"date": "2026-08-25T10:00:00Z",
"text": "just got back from the beach",
"pictureUrls": ["https://cdn.example.com/u/1042/beach.jpg"]
}
]
}Every URL must be publicly reachable at the moment we generate the answer, and HTTPS is strongly recommended. A photo is analyzed only if sharedPicturesAnalysis is true on that request.
Keep the message id meaningful on your side. It is what the rejection event gives you back.
Step 4: Ask for the Event
Open your project's settings and add chat.image.rejected to the list of events sent to your incoming content endpoint. The setup wizard does not offer it, so type it in.
Skip this step and everything else on this page is theory: the photo is still analyzed, and you are still billed, but nobody tells you.
Step 5: Handle Rejections and Find the Events Again
Rejections arrive on your Incoming Content Endpoint in this shape:
{
"event": "chat.image.rejected",
"data": {
"projectId": "prj_XXXXXXXXXXXX",
"sourceProfileId": "msg_1042",
"pictureUrl": "https://cdn.example.com/u/1042/beach.jpg"
}
}| Field | Use it for |
|---|---|
projectId | The project the rejection came from. |
sourceProfileId | Finding the conversation. Despite its name, this holds the id of the chat history message that carried the photo, not a profile id. |
pictureUrl | The photo to send to your moderation service. This is the URL you gave us. |
The full field reference is on the chat.image.rejected event page. Videos use the same shape under chat.video.rejected, with one important difference: the URL it carries is ours, not yours. The rest of the video behaviour is in the video guide.
How to find a rejection after the fact
- Log every event as it lands. Store
projectId,sourceProfileId,pictureUrland the time you received it. We do not keep the image, so your log is the only record that stays under your control. - Match
sourceProfileIdto your message. That gives you the conversation, the User and the moment it happened. - Open the Response Explorer to see the conversation around a rejection. Look at the reply that followed and the reply parameters that were active. That tells you whether vision was on and which model ran.
- Think the event never reached you? Check first that
chat.image.rejectedis in your project's event list (Step 4) — that is the usual answer. If it is, errors we hit when calling your endpoints show up in the same project logs: a timeout or a5xxappears there. - Still nothing? Contact support with the
projectId, thesourceProfileIdand roughly when it happened.
Frequently Asked Questions
Why are we not receiving any rejection events?
Almost always because chat.image.rejected is not in your project's event list. It is not there by default and the setup wizard does not offer it. See Step 4.
Do you keep our photos? No. We fetch the photo from your URL when we generate the answer, analyze it, and keep only the analysis. The image itself is never stored. Every URL has to be publicly reachable at that moment.
Do you ban a User who sends an illegal photo? No. We send you the event, and that is all. Bans, suspensions and legal reporting are yours.
Does the Persona still answer a photo you flagged? Yes. She receives its description like any other photo and replies normally. If you want the exchange to stop, you have to stop it on your side. Videos are the exception: a flagged video is withheld from her.
Are nude photos rejected?
Not by this check. The three reasons above are the only ones. How explicit the Persona gets, and how freely she shares her own photos, is set by pictureSharingMode. See Control Sharing and Sexual Intensity.
Is a flagged photo still billed? Yes, €0.015 like any other. We bill the analysis, and the moderation check happens after it.
What is sourceProfileId?
Its name is misleading. For a photo a User sent, it holds the id of the chat history message that carried the photo, not a profile id. Match it against your own messages to find the conversation. When the flagged photo is one the Persona was about to send, it does hold her profile id.
Does the event arrive before or after the answer? There is no guaranteed order. The two travel separately. In practice the rejection usually lands well before the answer, but do not build on that: handle each one when it arrives.
What if our endpoint is down when you send the event? We keep retrying a photo rejection until your endpoint accepts it. Video rejections behave differently and are not retried. See the video guide.
One message carries three photos and one is flagged. What happens to the other two? All three are analyzed and billed, all three are described to the Persona, and you receive one event for the flagged one.
What if a photo URL is unreachable when you generate the answer? We ignore the photo as if it had never been sent: no analysis, no charge, no event. The reply is written without it. We also stop trying that URL for a while, so fixing the link and asking for a reply straight away may change nothing. Wait a few minutes.
Why doesn't the Persona recognize the User in a selfie?
userProfilePictureAnalysis is off. To compare a shared photo against a face, she needs the profile picture analyzed too.
Why doesn't the Persona know about the photo album on our platform? Only the main profile picture reaches her. Albums are invisible, for the User and for herself.
Why did the Persona miss a photo entirely?
Check three things in that request: a 2.x model in chatModel, sharedPicturesAnalysis set to true, and the photo URL present in pictureUrls on the right message of your chat history response. If all three are right, the photo was probably unreachable when we fetched it.
We replaced a photo but kept the same URL. Is it analyzed again? Yes, once the stored analysis expires and we notice the file changed. See Pricing.
Can the Persona send a photo we flagged?
Yes. Your shareablePictures catalogue is not filtered, except on a forced share. Vet it on your side.
Is it the same for videos? Same design, separate options, separate price, and a few behaviours genuinely differ. See Vision Link: Video.