← Developers

Cookbook

Concrete, copy-paste answers to "how do I …?". Every recipe is a complete, runnable command. Once: create a token with the scopes you need, then put it and a small helper into your shell:

export VUTUV_TOKEN="vutuv_pat_..."
export API="https://vutuv.de/api/2.0"
auth() { curl -sS -H "Authorization: Bearer $VUTUV_TOKEN" "$@"; }

(Against a local development server, set API="http://localhost:4000/api/2.0".)

Details for every endpoint and field live in the reference; what the entities mean lives in the data model.

How do I read my own profile?

Scope profile:read:

auth $API/me

You get your profile as JSON — through your own eyes, so private email addresses are included. GET $API/users/<slug> reads anyone else's (you see what their profile page would show you, never more).

How do I post something?

Scope posts:write. The body is Markdown:

auth -X POST $API/posts \
  -H "Content-Type: application/json" \
  -d '{"body": "Hello from the API! **Markdown** works."}'

The answer is 201 with the full post, including its id and url. Add tags with a comma-separated string:

auth -X POST $API/posts \
  -H "Content-Type: application/json" \
  -d '{"body": "Talk slides are up!", "tags": "elixir, phoenix"}'

A post is public unless you exclude readers ("denials" — see the data model). For a connections-only post:

auth -X POST $API/posts \
  -H "Content-Type: application/json" \
  -d '{"body": "Connections only.", "denials": [{"wildcard": "non_connections"}]}'

How do I post an image?

Scope posts:write. Upload first (multipart), then attach by id:

IMAGE_ID=$(auth -X POST $API/me/post_images \
  -F "image=@photo.jpg" -F "alt=Sunrise over Koblenz" | jq -r .id)

auth -X POST $API/posts \
  -H "Content-Type: application/json" \
  -d "{\"body\": \"What a morning!\", \"image_ids\": [\"$IMAGE_ID\"]}"

Formats and limits: see Images in the data model.

How do I send a direct message?

Scope messages:write. Send to a member by their username — this finds the existing conversation or starts a new one:

auth -X POST $API/users/wintermeyer/messages \
  -H "Content-Type: application/json" \
  -d '{"body": "Hello Stefan! Your talk was great."}'

201 with the message. Mind the request model: if the recipient does not follow you yet, your first message opens a request that they accept or decline — and until they accept, you cannot send a second one (409, reason: pending_limit). If they follow you, your message lands directly.

How do I read my direct messages?

Scope messages:read. First the inbox — accepted conversations (plus your own outgoing requests) under conversations, incoming requests from strangers under requests:

auth $API/conversations
{
  "type": "conversations",
  "conversations": [{"id": "0190…", "status": "accepted",
                     "with": {"name": "Greta Tester", "slug": "greta-tester", "url": "…"},
                     "preview": "See you there!", "last_message_at": "2026-06-12T09:30:00", "unread": 2}],
  "requests": []
}

Then a thread, newest first, cursor-paginated:

auth "$API/conversations/0190…/messages?limit=30"

Each message carries body_markdown, from, sent_at and mine (true for the ones you sent). Afterwards, clear your unread counter:

auth -X POST $API/conversations/0190…/read

How do I accept or decline a message request?

Scope messages:write. An entry from requests (above):

auth -X POST $API/conversations/0190…/accept    # talk
auth -X POST $API/conversations/0190…/decline   # silently decline

Declining is invisible to the sender: to them the request just stays pending.

How do I follow someone — or connect?

Scope social:write. Follow = one-directional subscription (their posts appear in your feed), no consent needed:

auth -X PUT $API/users/wintermeyer/follow      # follow
auth -X DELETE $API/users/wintermeyer/follow   # unfollow

Connection = mutual and consented, like on LinkedIn — you request, they accept:

auth -X POST $API/users/wintermeyer/connection

Where do you stand with someone? One call:

auth $API/users/wintermeyer/relationship

How do I read my feed?

Scope posts:read. Your timeline — your posts plus posts and reposts of everyone you follow, newest first:

auth "$API/feed?limit=25"

Page with the returned next_cursor until more is false:

auth "$API/feed?cursor=NEXT_CURSOR_FROM_LAST_PAGE"

How do I reply to, like, or repost a post?

Scope posts:write. A reply is a normal post attached to a public parent; like/bookmark/repost are idempotent switches (PUT on, DELETE off):

auth -X POST $API/posts/0190…/replies \
  -H "Content-Type: application/json" \
  -d '{"body": "Great point!"}'

auth -X PUT $API/posts/0190…/like
auth -X PUT $API/posts/0190…/repost
auth -X DELETE $API/posts/0190…/like

How do I update my profile?

Scope profile:write. Plain fields via PATCH /me:

auth -X PATCH $API/me \
  -H "Content-Type: application/json" \
  -d '{"headline": "Phoenix & Elixir consulting. Available from August."}'

Section entries (work experience, links, …) via their own endpoints:

auth -X POST $API/me/work_experiences \
  -H "Content-Type: application/json" \
  -d '{"title": "Senior Developer", "organization": "ACME", "start_year": 2024, "start_month": 3}'

auth -X POST $API/me/links \
  -H "Content-Type: application/json" \
  -d '{"value": "https://example.com/blog", "description": "My blog"}'

auth -X POST $API/me/tags \
  -H "Content-Type: application/json" \
  -d '{"name": "Phoenix"}'

How do I check my notifications?

Scope social:read — the same feed the bell icon shows (new followers, endorsements, connection events, replies, likes):

auth $API/notifications

unread carries the count; POST $API/notifications/read (scope social:write) moves your read marker.

How do I read public data without any token?

Every public page is also served machine-readable under its own URL plus .json, .md or .txt (profiles additionally as .vcf) — the anonymous view, no account needed:

curl https://vutuv.de/wintermeyer.json        # a profile
curl https://vutuv.de/wintermeyer/posts.json  # their post archive
curl https://vutuv.de/tags/phoenix.json              # a tag page

The full list of public pages: /llms.txt.

Something failed — now what?

Errors are JSON problem documents with a human-readable detail that says what is wrong (missing scope, rate limit, validation error, …) — read it, it is written to be read. The catalog: errors.