Skip to content
Docs
opendesk.shOpen app

Pictures, assignment and setup

Profile pictures and logos, agent availability, assignment rules, triage, business hours and the setup checklist.

Method Path Description
PUT, DELETE /v1/me/avatar Upload or remove your own picture
POST /v1/me/heartbeat Mark yourself as seen (204), for auto-away
PATCH /v1/members/{user_id}/availability Set availability and the assignment cap. user_id may be me
PUT, DELETE /v1/workspace/images/{slot} Workspace logo and brand images (admin)
PUT, DELETE /v1/contacts/{id}/avatar Upload or remove a contact picture (agent)
PUT, DELETE /v1/companies/{id}/avatar Upload or remove a company logo (agent)
POST /v1/contacts/{id}/avatar/refresh, /v1/companies/{id}/avatar/refresh Look the automatic sources up again (202)
GET /v1/images/{id}?s=256|64 The processed picture behind every avatar_url (public)
GET, PATCH /v1/assignment Workspace mode, per mailbox and team rules, auto-away
GET, PATCH /v1/triage Ollie’s triage settings
GET, PUT /v1/business-hours Opening hours, holidays and timezone
GET /v1/onboarding The setup checklist for the current workspace
POST /v1/onboarding/dismiss Hide the checklist for yourself (204)

Reads need any member. Writes to assignment, triage, business hours and workspace images need an admin.

type AvatarSource = "upload" | "oauth" | "gravatar" | "domain" | "widget";
// On User, Member.user, Contact, Company and Workspace. null means no picture: show initials.
avatar_url: string | null;
avatar_source: AvatarSource | null;
type Workspace = {
// ...
avatar_url: string | null; // the workspace logo
avatar_source: "upload" | null;
images: {
help_center_logo: string | null;
help_center_favicon: string | null;
widget_avatar: string | null;
widget_launcher: string | null;
};
};
type Availability = "online" | "away" | "offline";
type Member = {
// ...
availability: Availability; // what the member set
presence: Availability; // availability, or "away" when auto-away applies
max_open_tickets: number | null;
open_tickets: number;
last_seen_at: string | null;
};
type AssignMode = "none" | "round_robin" | "load_balanced";
type Assignment = {
mode: AssignMode;
rules: { target_type: "mailbox" | "team"; target_id: string; mode: AssignMode }[];
auto_away_minutes: number; // 0 = off, else 10..1440
};
type TriageSettings = {
auto_triage: boolean;
fields: ("priority" | "severity" | "labels" | "team")[]; // default priority, severity, labels
assign_after_triage: boolean;
threshold: number; // 0.5..0.99, default 0.7: confidence at or above it applies a decision, below it leaves a suggestion
engine: { provider: "typesafe" | "llm" | "none"; model: string; connected: boolean };
available: boolean; // triage can run: AI or a decision engine is configured, and AI is on for the workspace
};
type BusinessHours = {
enabled: boolean;
timezone: string;
schedule: Record<"mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun", { start: string; end: string }[]>;
holidays: { date: string; name: string }[];
};
type Onboarding = {
steps: { id: string; title: string; description: string; done: boolean; href: string; requires: "admin" | "agent" }[];
done_count: number;
total: number;
dismissed: boolean;
};

Uploads are multipart/form-data with one file field: PNG, JPEG, GIF or WebP, at most 5 MB and 4096 px a side.

Terminal window
curl -X PUT https://api.opendesk.sh/v1/me/avatar \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-F file=@me.jpg

The response is the updated object. Every picture is decoded, cropped to a square (logos are fitted instead), resized to 256 and 64 px and re-encoded as PNG without metadata. The type is read from the content, not the file name or header.

Status Code When
413 too_large The body is over 5 MB
422 unsupported_image Not a PNG, JPEG, GIF or WebP image (SVG is refused)
422 image_too_large Over 4096 px a side or 16 megapixels, or under 16 px

Workspace slots are logo, help_center_logo, help_center_favicon, widget_avatar and widget_launcher:

Terminal window
curl -X PUT https://api.opendesk.sh/v1/workspace/images/help_center_logo \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "OD-Workspace: $WORKSPACE_ID" \
-F file=@logo.png

PATCH /v1/me with {"avatar_url": "https://..."} copies a picture from a URL instead. It is fetched server-side, and member.updated follows when it is ready.

GET /v1/images/{id} needs no authentication. Ids are random and each image is immutable, so a new picture always gets a new URL. Responses carry Cache-Control: public, max-age=31536000, immutable, X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none'; sandbox. Add ?s=64 for the small size.

Terminal window
curl -X PATCH https://api.opendesk.sh/v1/members/me/availability \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "OD-Workspace: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "availability": "away" }'

Members set their own availability. Admins set anyone’s, and max_open_tickets (1 to 1000, null clears). Viewers are never assigned, so a cap for a viewer is refused with 422.

Terminal window
curl -X PATCH https://api.opendesk.sh/v1/assignment \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "OD-Workspace: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "mode": "round_robin", "auto_away_minutes": 30,
"rules": [{ "target_type": "team", "target_id": "TEAM_ID", "mode": "load_balanced" }] }'

rules replaces the whole list. See Triage and assignment for how a ticket’s mode and assignee are chosen.

Terminal window
curl -X PATCH https://api.opendesk.sh/v1/triage \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "OD-Workspace: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "auto_triage": true, "fields": ["priority", "severity"], "threshold": 0.8, "assign_after_triage": true }'

engine.provider is typesafe when Ollie decides using Jev by TypeSafe, llm when the configured AI model decides (no confidence gate), or none.

PUT takes the full object. Up to 3 ranges a day on the quarter hour, 24:00 ends a day, and up to 100 holidays.

Terminal window
curl -X PUT https://api.opendesk.sh/v1/business-hours \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "OD-Workspace: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "enabled": true, "timezone": "Europe/Budapest",
"schedule": { "mon": [{ "start": "09:00", "end": "17:00" }], "tue": [{ "start": "09:00", "end": "17:00" }],
"wed": [{ "start": "09:00", "end": "17:00" }], "thu": [{ "start": "09:00", "end": "17:00" }],
"fri": [{ "start": "09:00", "end": "13:00" }], "sat": [], "sun": [] },
"holidays": [{ "date": "2026-12-25", "name": "Christmas" }] }'

SLA policies with business_hours: true count only open minutes.

Each step is computed from the workspace’s real state, so it is done as soon as the thing exists. Step ids are connect_mailbox, invite_teammate, upload_photo, install_widget, publish_article, setup_triage, create_label and try_ollie.

Event When
member.updated A member’s picture, availability or cap changed
contact.updated A contact’s picture changed
company.updated A company’s logo changed
workspace.updated The workspace logo, a brand image or settings changed

Audit log actions: user.avatar_updated, user.avatar_removed, contact.avatar_updated, contact.avatar_removed, company.avatar_updated, company.avatar_removed, workspace.image_updated, workspace.image_removed, member.assignment_cap_changed, assignment.updated, triage.updated and business_hours.updated.

Edit this page on GitHub