API reference
Introduction
The Vibe API is REST over HTTPS. It takes JSON, returns JSON, and every response has the same envelope: a boolean success, a human-readable message, and data on success.
Resources are addressed by uuid. Each one also has a reference (CSE-000517, ORG-000001) which is what you show a person and what they will quote back at you — it addresses nothing, so it stays free to change.
Vibing is scheduled. A cause is only open for vibes between the open_at its creator set and the close_at that follows from its duration. Every cause carries a vibe_schedule whose status is scheduled, live or closed — derived on every read, so a Vibe closes on its own with nothing to poll and no callback to wait for. Build your UI around that field, not around lifecycle_status.
https://vibe-api.onaotc.com/api/v1
Authentication
Send an API key on every request, as X-API-Key or as a bearer token. A key identifies an organisation, not a person — there is no user session involved.
Generate one per site you integrate. Your plan sets how many can be live at once, and revoking one takes effect immediately.
Keep keys server-side. A key can record vibes as you, read your members and spend your monthly allowance. Calling this API from browser JavaScript hands it to every visitor — proxy through your own backend instead.
A key is shown once, when you create it. If you lose it, revoke and generate another; nothing can reveal it again.
curl https://vibe-api.onaotc.com/api/v1/me \ -H "X-API-Key: $VIBE_API_KEY" # a bearer token works too curl https://vibe-api.onaotc.com/api/v1/me \ -H "Authorization: Bearer $VIBE_API_KEY"
Creating a cause
The one task that spans several endpoints in a fixed order. Everything else in this reference is a single call; this is worth reading once end to end.
- 1Pick a category — or name a new one
GET /v1/categoriesreturns the active categories; send theuuidyou want ascategory_uuid. If none fits, skip this call and sendcategory_nameinstead — an existing name is reused (case-insensitively) and a new one is created. Never send both: the name wins and the uuid is ignored. - 2Upload the cover image — optional
POST /v1/uploads/imageas multipart, with the file infileandfolder=causes. It stores the file and returns a path; it does not attach it to anything. Keep theurlfor the next step. A cause is perfectly valid with no cover, so skip this if you have no image. - 3Create the cause
POST /v1/causeswith the title, the category from step 1, theurlfrom step 2 asimage_url, and the Vibe schedule —open_at(with a timezone offset) andduration_seconds, both required. Add the repeat fields if the window should come back around. - 4Read it back from your own list
GET /v1/causes/mine/{uuid}, not the public catalogue — that one only ever returns approved, public causes, so a cause awaiting review would 404 on it at exactly the moment you want to see it. Branch onmoderation_status.
# 1 — a category to file it under
CATEGORY=$(curl -s https://vibe-api.onaotc.com/api/v1/categories \
-H "X-API-Key: $VIBE_API_KEY" \
| jq -r '.data.categories[0].uuid')
# 2 — the cover image (optional). Returns a path, attaches nothing.
COVER=$(curl -s -X POST https://vibe-api.onaotc.com/api/v1/uploads/image \
-H "X-API-Key: $VIBE_API_KEY" \
-F "file=@cover.png" -F "folder=causes" \
| jq -r '.data.url')
# 3 — the cause itself. open_at + duration_seconds are required.
CAUSE=$(curl -s -X POST https://vibe-api.onaotc.com/api/v1/causes \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Ten minutes for clean water\",
\"description\": \"Hold the button and give ten minutes.\",
\"category_uuid\": \"$CATEGORY\",
\"image_url\": \"$COVER\",
\"open_at\": \"2026-09-07T17:00:00+05:30\",
\"duration_seconds\": 600,
\"repeat_frequency\": \"custom\",
\"repeat_days\": [1, 3, 5],
\"repeat_count\": 6
}" \
| jq -r '.data.cause.uuid')
# 4 — read it back from YOUR list, whatever its status
curl -s https://vibe-api.onaotc.com/api/v1/causes/mine/$CAUSE \
-H "X-API-Key: $VIBE_API_KEY"
Two things trip people up. A cause is not vibeable the moment it exists — it opens on its schedule, and moderation_status: approved is not the same as being open. And an image is always two calls: there is no way to post a cause and its file together.
Errors
A failure keeps the same envelope: success is false and message is written to be shown to a person. Validation failures add a per-field errors array — read that rather than parsing the message.
- 400
- The request was malformed — or refused on a rule, e.g. a vibe outside the cause’s Vibe window, or a schedule that has already finished.
- 401
- The key is missing, unrecognised, or revoked.
- 403
- The organisation this key belongs to is not active.
- 404
- No such record, or one this key may not see.
- 422
- A parameter failed validation — see
errors. - 429
- Rate limited. Back off and retry.
{
"success": false,
"message": "Validation failed",
"errors": [
{ "field": "cause_uuid", "message": "cause_uuid is required" },
{ "field": "seconds", "message": "seconds must be greater than or equal to 1" }
]
}Rate limits
Two limits apply. A hard ceiling of 600 requests per 15 minutes per IP, which returns 429 — and your plan's monthly request allowance, counted across all your keys and visible on the API Access screen.
Usage is counted per organisation, so rotating keys does not reset it.
Your own usage and remaining allowance are on the API Access screen once you sign in.
{
"success": false,
"message": "Too many requests. Please try again shortly."
}Causes
What people can give their time to, and the scheduled window each one is open for.
Create a cause
POST/v1/causes
Publishes a cause for your organisation, exactly as creating one in this portal would.
- •A Vibe schedule is required. A cause is not open for vibes the moment it exists: vibing opens at
open_atand closes on its own onceduration_secondshave passed. Both fields are required, and there is no "always open" option — whoever creates the cause decides its window, and the people who vibe it never choose a duration. - •
open_atmust carry a timezone offset (Zor±HH:MM). An offset-less time is refused: "5:00 PM" read in the wrong zone opens a Vibe hours late, and only you know which 5 PM you meant. - •The window can repeat —
daily,weekly,monthly,yearly, or on chosen weekdays withcustom. A repeat does NOT let anyone vibe the cause twice (a person vibes a cause once, full stop); it re-opens the window for people who have not vibed it yet. - •A repeating cause needs exactly one end:
repeat_untilorrepeat_count, never both and never neither. An open-ended series nobody remembers to close runs for ever. - •
repeat_daysbelongs tocustomalone, and occurrence one is the open date — soopen_athas to fall on one of the days you chose, or the series would begin off its own rule. Weekdays are0= Sunday …6= Saturday. - •
monthlyandyearlyclamp the day to the target month: a series anchored on 31 January runs 28 February, not 3 March. A series is capped at 366 occurrences. - •A window that has already finished is refused. That includes one that ends between your call and now, so schedule forward rather than backfilling.
- •The response carries a derived
vibe_schedule. Itsstatus—scheduled,liveorclosed— is computed on every read, so a Vibe closes on its own: nothing has to run, and there is no callback to wait for. Readstatus, notlifecycle_status, to decide whether a cause can be vibed. - •The cause is attributed to your organisation’s owner — the person who authorised the key. There is no system account.
- •
is_publicreaches everyone on Vibe rather than only your members, and is a paid feature: on a plan without it the call is refused with a 403 rather than quietly creating a private cause. - •Branch on
moderation_status, not on the message:approvedmeans it has cleared review,pendingmeans it is queued for it. Which one you get depends on your organisation’s auto-approval setting. - •Approved is not the same as open. An approved cause still waits for its window, and a cause left in review past its
open_atmisses it — so leave enough lead time for review, or reschedule it afterwards with the edit endpoint. - •Do not send
organisation_uuid— a key already names its organisation, and passing one is refused. - •A category is required, and there are two ways to give one. Send
category_uuidfromGET /v1/categoriesto file the cause under an existing category, or sendcategory_nameas free text. - •
category_nameis FIND-OR-CREATE, matched case-insensitively: "clean water" files the cause under the existing "Clean Water" rather than making a second one, and a name nobody has used yet creates the category on the spot. A category retired earlier under that name is revived rather than duplicated. - •A category created this way is created platform-wide, not privately to you — it appears in
GET /v1/categoriesfor everyone immediately, and the name is screened for abuse. Prefercategory_uuidfrom the list when a suitable category already exists; use a name when you genuinely need a new one. - •Do not send both. They are not merged and it is not an error:
category_namewins andcategory_uuidis ignored, so a stale name alongside a correct uuid quietly files the cause somewhere else. The sample opposite shows every OTHER field at once, but only one category field. - •Creating a cause under a brand-new category is just the name — there is no separate call to make first:
{ "title": "Saturday beach clean", "open_at": "2026-09-12T09:00:00+05:30", "duration_seconds": 900, "category_name": "Beach cleanups" }. The response echoes the category it landed in, with theuuidyou would reuse next time. - •A cover image is two calls, in this order: upload the file (next endpoint), then send the
urlit returns back here asimage_url. There is no way to post a cause and its image together — see "Creating a cause" in the guide for the whole sequence. - •The sample opposite shows every field. Only
titleand a category are required — send as few of the rest as you like. - •
goalis stored but not echoed here: this response is the public shape of a cause. Read it back fromGET /v1/causes/mineto see it.
Body / path parameters
titlestringrequired- 3–300 characters.
open_atstringrequired- When vibing opens. ISO 8601 including a timezone offset, e.g.
2026-09-05T17:00:00+05:30. duration_secondsintegerrequired- How long the Vibe stays open, 60–86400. The Vibe app offers 300 / 600 / 900 as one-tap choices plus a custom value; the wire format is always seconds.
repeat_frequencyenumoptionalnone(default),daily,weekly,monthly,yearlyorcustom. Omit for a one-off.repeat_daysinteger[]optionalcustomonly. Weekdays the window opens on,0= Sunday …6= Saturday.repeat_untilstringoptionalYYYY-MM-DD. The series runs to the end of this day. Use this ORrepeat_count.repeat_countintegeroptional- Total occurrences, 2–366. Use this OR
repeat_until. category_uuiduuidoptional- An existing category, from
GET /v1/categories. One of this orcategory_nameis required. category_namestringoptional- A category by name, 2–80 characters. Reused if it already exists (case-insensitive), created platform-wide if not. Wins over
category_uuidif both are sent. descriptionstringoptional- Up to 500 characters.
is_publicbooleanoptional- Defaults to false — visible to your members only.
image_urlstringoptional- A path returned by the upload endpoint below. Absolute URLs are refused.
organisation_namestringoptional- The beneficiary named on the cause, if it is not you.
goalstringoptional- A short note on what you are aiming for. Free text, not a number — Vibe measures time, not money.
curl -X POST https://vibe-api.onaotc.com/api/v1/causes \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Ten minutes for clean water", "description": "Hold the button and give ten minutes of focus.", "open_at": "2026-09-07T17:00:00+05:30", "duration_seconds": 600, "repeat_frequency": "custom", "repeat_days": [1, 3, 5], "repeat_count": 6, "category_uuid": "5f33351f-3606-4efc-b20a-0519b3043972", "is_public": false, "image_url": "/uploads/causes/1788256409989-c3a2689e2b38736d.png", "organisation_name": "Water Trust", "goal": "500 hours before the end of the quarter" }'{
"success": true,
"message": "Cause created successfully and is now live.",
"data": {
"cause": {
"uuid": "0101f2c4-8433-471d-a503-a8dfacff944e",
"reference": "CSE-000597",
"title": "Ten minutes for clean water",
"is_public": false,
"description": "Ten minutes of focus for clean water.",
"image_url": null,
"category": { "uuid": "5f33351f-…", "reference": "CAT-0007", "name": "Water" },
"organisation": { "uuid": "69f9fa05-…", "reference": "ORG-000001", "name": "Helping Hands Foundation" },
"moderation_status": "approved",
"lifecycle_status": "active",
"vibe_schedule": {
"open_at": "2026-09-07T11:30:00.000Z",
"close_at": "2026-09-07T11:40:00.000Z",
"duration_seconds": 600,
"status": "scheduled",
"repeat": {
"frequency": "custom",
"anchor_at": "2026-09-07T11:30:00.000Z",
"days": [1, 3, 5],
"until": null,
"count": 6,
"occurrence": 1,
"total_occurrences": 6,
"next_open_at": "2026-09-09T11:30:00.000Z",
"series_ends_at": "2026-09-18T11:40:00.000Z"
},
"seconds_until_open": 8112,
"seconds_remaining": null,
"closed_early": false
},
"submitted_by": {
"reference": "USR-000002",
"first_name": "Admin",
"last_name": "Pawar"
},
"created_at": "2026-09-01T09:10:41.518Z"
}
}
}Upload an image
POST/v1/uploads/image
Takes a file and hands back the path to store — step one of attaching a cover image to a cause, or a logo to your organisation.
- •A multipart form, not JSON. The file field is
file. - •Uploading stores the file and nothing else. It does not attach it to anything: send the returned
urlto the endpoint that should use it, asimage_urlon a cause orlogo_urlon your organisation. An upload nobody references is simply an orphaned file. - •
folderiscausesfor a cover image ororg-logosfor a logo. Anything else — including omitting it — files the image undermisc. That still works and the path is still valid, but nothing sorts it later, so pass the right one. - •Limits: 5 MB, and PNG, JPEG, WebP, GIF, SVG, HEIC or HEIF. Anything else is refused on the content type, not the file extension.
- •The path is host-relative on purpose:
image_urlandlogo_urlonly accept a reference this endpoint returned. An absolute URL is refused, so a live cause or logo can never be pointed at a host we do not control. - •Nothing expires. A
urlyou got last month is still valid to attach today, so uploading early and creating the cause later is fine.
Body / path parameters
filefilerequired- The image itself. Up to 5 MB; PNG, JPEG, WebP, GIF, SVG, HEIC or HEIF.
folderstringoptionalcausesfor a cover image,org-logosfor a logo. Anything else lands inmisc.
curl -X POST https://vibe-api.onaotc.com/api/v1/uploads/image \ -H "X-API-Key: $VIBE_API_KEY" \ -F "file=@cover.png" \ -F "folder=causes"
{
"success": true,
"message": "File uploaded successfully.",
"data": {
"url": "/uploads/causes/1788253842198-0ef6a2fe3c891e90.png"
}
}List categories
GET/v1/categories
The categories a cause can be filed under — what to populate a category picker from before creating one.
- •Take
uuidfrom here and send it ascategory_uuidwhen you create a cause. That is the reliable way to file a cause under an existing category. - •Active categories only, the same list the Vibe app's own picker reads. A retired category stays on the causes already using it but is not offered for new ones — so a uuid from an older response can stop appearing here while the causes under it keep working.
- •Platform-wide, not per-organisation: categories are shared by everyone on Vibe, and there is no way for a key to create one directly. Creating a cause with a
category_namenobody has used yet is what adds one. - •
reference(CAT-0001) is for showing a person;uuidis what the API takes. Unpaged by default — passlimitto page, andsearchto filter by name.
Query parameters
searchstringoptional- Match the category name.
pageintegeroptional- Only meaningful alongside
limit. Defaults to 1. limitintegeroptional- 1–100. Omit entirely to get the full list in one call, which is what a picker wants.
curl "https://vibe-api.onaotc.com/api/v1/categories?search=water&page=1&limit=100" \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Categories fetched successfully.",
"data": {
"categories": [
{
"uuid": "af699488-74c9-4efc-8711-4aea7cbb74c5",
"reference": "CAT-0001",
"name": "Education",
"description": "Improving access to quality education and learning opportunities.",
"is_active": true,
"deleted": false,
"created_at": "2026-08-06T17:46:44.590Z",
"updated_at": "2026-08-29T07:05:12.007Z"
}
]
}
}Managing causes
Read back, edit, moderate and retire the causes your organisation already has.
List your causes
GET/v1/causes/mine
Your organisation’s causes at every status — including private ones and those still waiting on review.
- •This is YOUR list, not the public catalogue: a cause you have just created does not appear publicly until it is both approved and public, so this is the endpoint to read it back from.
- •Paged. Filter with
moderation_status(pending,approved,rejected) andlifecycle_status(active,closed). - •
statsis the impact recorded so far: how many people took part, and how long for. - •Filter by Vibe window too, with
vibe_status. Unlike the public catalogue, nothing is hidden here by default — this is your full list, finished Vibes included. - •On a repeating cause
vibe_status=scheduledincludes one that is merely between occurrences, andclosedonly ever means the series itself is finished.repeat.occurrence/repeat.total_occurrencessay how far through it is. - •Submitters are named, never emailed — an integration gets a name and a reference, not a way to contact your members.
- •There is no organisation filter: a key only ever sees its own organisation, so there is nothing to narrow.
- •The sample opposite passes every filter at once. They are all optional — send only the ones you want.
Query parameters
moderation_statusstringoptionalpending,approvedorrejected.lifecycle_statusstringoptionalactiveorclosed. The manual hold, not the schedule.vibe_statusenumoptionallive,scheduledorclosed. A third axis: an approved, un-held cause is still unvibeable until its window opens.category_uuiduuidoptional- Only causes in one category.
searchstringoptional- Matches the title and description.
organisation_namestringoptional- Match the beneficiary named on the cause.
start_datestringoptionalYYYY-MM-DD. Created on or after.end_datestringoptionalYYYY-MM-DD. Created on or before.pageintegeroptional- Defaults to 1.
limitintegeroptional- 1–100. Defaults to 20.
curl "https://vibe-api.onaotc.com/api/v1/causes/mine?moderation_status=approved&lifecycle_status=active&vibe_status=live&category_uuid=5f33351f-3606-4efc-b20a-0519b3043972&search=clean%20water&organisation_name=Water%20Trust&start_date=2026-08-01&end_date=2026-09-01&page=1&limit=20" \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Causes fetched successfully.",
"data": {
"causes": [
{
"uuid": "729afc2d-e985-4fe4-bdeb-669888fa7b78",
"reference": "CSE-000687",
"title": "Ten minutes for clean water",
"is_public": false,
"description": "",
"image_url": null,
"category": { "uuid": "5f33351f-…", "reference": "CAT-0007", "name": "Water" },
"moderation_status": "approved",
"lifecycle_status": "active",
"vibe_schedule": {
"open_at": "2026-09-05T11:30:00.000Z",
"close_at": "2026-09-05T11:40:00.000Z",
"duration_seconds": 600,
"status": "closed",
"repeat": {
"frequency": "none",
"anchor_at": "2026-09-05T11:30:00.000Z",
"days": [],
"until": null,
"count": null,
"occurrence": 1,
"total_occurrences": 1,
"next_open_at": null,
"series_ends_at": "2026-09-05T11:40:00.000Z"
},
"seconds_until_open": null,
"seconds_remaining": null,
"closed_early": false
},
"rejection_reason": null,
"submitted_by": {
"uuid": "70a89cd1-…",
"reference": "USR-000002",
"first_name": "Admin",
"last_name": "Pawar"
},
"stats": { "participants": 0, "total_seconds": 0, "avg_seconds": 0 },
"created_at": "2026-09-01T09:30:58.231Z",
"reviewed_at": "2026-09-01T09:30:58.230Z"
}
],
"meta": { "page": 1, "limit": 20, "total": 46, "total_pages": 3 }
}
}Retrieve one of your causes
GET/v1/causes/mine/{uuid}
One cause in full, with the individual vibes given to it. Works whatever its status.
- •Use this for your own causes whatever their state — a public read only ever returns approved, public ones.
- •
vibesnames each contribution by reference and seconds — no names or emails, andsourcesays whether it came from the Vibe app or through your integration.
Body / path parameters
uuiduuidrequired- The cause.
curl https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Cause fetched successfully.",
"data": {
"cause": {
"uuid": "729afc2d-e985-4fe4-bdeb-669888fa7b78",
"reference": "CSE-000687",
"title": "Ten minutes for clean water",
"moderation_status": "approved",
"lifecycle_status": "active",
"vibe_schedule": {
"open_at": "2026-09-05T11:30:00.000Z",
"close_at": "2026-09-05T11:40:00.000Z",
"duration_seconds": 600,
"status": "live",
"repeat": {
"frequency": "weekly",
"anchor_at": "2026-08-24T11:30:00.000Z",
"days": [],
"until": null,
"count": 4,
"occurrence": 2,
"total_occurrences": 4,
"next_open_at": "2026-09-12T11:30:00.000Z",
"series_ends_at": "2026-09-19T11:40:00.000Z"
},
"stats": { "participants": 0, "total_seconds": 0, "avg_seconds": 0 }
},
"vibes": []
}
}Edit a cause
PUT/v1/causes/mine/{uuid}
Changes the wording, category, cover image or Vibe schedule of a cause. Send only the fields you are changing.
- •A content edit, not a re-review: the moderation status is left exactly as it was.
- •At least one field is required. The sample opposite shows every field you may send — pick the ones you are changing.
- •This is how you reschedule.
open_atandduration_secondsmay be sent alone — sendduration_secondsby itself to extend or shorten a Vibe that is already running, and the storedopen_atis kept. - •Send neither and the window is left untouched. That is what lets you fix the wording of a cause whose Vibe has already closed, which re-sending its old schedule would not.
- •A resulting window that has already finished is refused. Use the close endpoint to end a cause early rather than backdating its schedule — closing notifies the submitter, and an edit does not. On a repeating cause it is the whole SERIES that must not be over, so one whose early occurrences have passed can still be edited.
- •The repeat fields merge the same way: change
repeat_frequencyalone and the end you already chose is kept. `repeat_frequency: "none"` is how a repeat is turned off, and it clears the rule with it. - •Moving the frequency off
customdrops the stored weekdays — that is what the change means. Weekdays sent explicitly alongside a non-custom frequency are refused instead, because silently dropping what someone asked for is worse than saying it does not apply. - •
organisation_uuidis refused — a cause cannot be moved to another organisation through a key.
Body / path parameters
titlestringoptional- 3–300 characters.
descriptionstringoptional- Up to 500 characters.
open_atstringoptional- A new open time. ISO 8601 with a timezone offset. Sent alone, the stored duration is kept.
duration_secondsintegeroptional- A new length, 60–86400. Sent alone, the stored open time is kept.
repeat_frequencyenumoptional- Change the cadence, or send
noneto stop repeating. repeat_daysinteger[]optionalcustomonly.0= Sunday …6= Saturday.repeat_untilstringoptionalYYYY-MM-DD. Replaces whichever end was set before.repeat_countintegeroptional- Total occurrences, 2–366.
category_uuiduuidoptional- Move it to another category.
image_urlstringoptional- A path returned by the upload endpoint.
organisation_namestringoptional- The beneficiary named on the cause.
category_namestringoptional- A category by name, created if new. Use this or
category_uuid. goalstringoptional- A short note on what you are aiming for.
curl -X PUT https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Ten minutes for clean water", "description": "Hold the button and give ten minutes of focus.", "open_at": "2026-09-07T17:00:00+05:30", "duration_seconds": 900, "repeat_frequency": "weekly", "repeat_count": 8, "category_uuid": "5f33351f-3606-4efc-b20a-0519b3043972", "image_url": "/uploads/causes/1788256409989-c3a2689e2b38736d.png", "organisation_name": "Water Trust", "goal": "500 hours before the end of the quarter" }'{
"success": true,
"message": "Cause updated successfully.",
"data": {
"cause": {
"uuid": "729afc2d-e985-4fe4-bdeb-669888fa7b78",
"title": "Ten minutes for clean water",
"description": "Ten minutes of focus for clean water.",
"moderation_status": "approved",
"lifecycle_status": "active",
"vibe_schedule": {
"open_at": "2026-09-06T11:30:00.000Z",
"close_at": "2026-09-06T11:45:00.000Z",
"duration_seconds": 900,
"status": "scheduled",
"seconds_until_open": 94512,
"seconds_remaining": null,
"closed_early": false
}
}
}
}Approve a cause
POST/v1/causes/mine/{uuid}/approve
Publishes a cause one of your members submitted. This is your organisation’s own review queue.
- •Only a cause still
pendingcan be approved — anything else is a 400. - •Approving does not move the schedule. A cause reviewed after its
open_athas passed misses that window — on a one-off it needs rescheduling, and on a repeating one it simply starts from the next occurrence. Checkvibe_schedule.statusin the response rather than assuming it is now live. - •Your members’ submissions only. A cause that reaches everyone on Vibe is reviewed by Vibe staff, and a key is refused on those.
- •The submitter is notified, and once it is live your members are told about it.
- •You will only see a queue here if your organisation lets members post and has auto-approval switched off.
Body / path parameters
uuiduuidrequired- The pending cause.
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/approve \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Cause approved successfully.",
"data": {
"cause": {
"uuid": "1c1b7f4e-…",
"title": "Member submitted cause",
"moderation_status": "approved",
"rejection_reason": null,
"lifecycle_status": "active"
}
}
}Reject a cause
POST/v1/causes/mine/{uuid}/reject
Declines a submission and tells the person who filed it why.
- •
reasonis required, 3–500 characters. It is shown to the submitter, so write it for them. - •Only a cause still
pendingcan be rejected.
Body / path parameters
reasonstringrequired- Why it was declined. Shown to the submitter.
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/reject \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Not a fit for this quarter." }'{
"success": true,
"message": "Cause rejected successfully.",
"data": {
"cause": {
"uuid": "1c1b7f4e-…",
"moderation_status": "rejected",
"rejection_reason": "Not a fit for this quarter."
}
}
}Close a cause
POST/v1/causes/mine/{uuid}/close
Ends a Vibe EARLY, before its scheduled window runs out. It stops accepting vibes but keeps everything already given to it.
- •A manual hold that overrides the schedule: a cause closed this way reads as
closedeven while its window is still open, andvibe_schedule.closed_earlyis how you tell that apart from a window that simply ran out. - •On a repeating cause this stops the whole series, not one occurrence. The hold overrides the schedule outright, so every remaining occurrence is dead until you reopen it. There is no way to skip a single occurrence — shorten the series with the edit endpoint instead.
- •A cause whose window has already passed needs no closing — it is closed. Calling this on one changes nothing a visitor can see.
Body / path parameters
uuiduuidrequired- The cause.
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/close \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Cause closed successfully.",
"data": {
"cause": {
"uuid": "729afc2d-…",
"lifecycle_status": "closed",
"vibe_schedule": { "status": "closed", "closed_early": true }
}
}
}Reopen a cause
POST/v1/causes/mine/{uuid}/reactivate
Lifts the manual hold and hands the cause back to its Vibe schedule.
- •This does not grant a new window. If the scheduled one has already passed, the cause stays closed — give it a new schedule with the edit endpoint to run it again. On a repeating cause with occurrences left, lifting the hold hands it straight back to its series.
- •Check
vibe_schedule.statusin the response rather than assuming:livemeans it is taking vibes again,scheduledthat it will,closedthat it needs rescheduling.
Body / path parameters
uuiduuidrequired- The closed cause.
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/reactivate \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Cause reactivated successfully.",
"data": {
"cause": {
"uuid": "729afc2d-…",
"lifecycle_status": "active",
"vibe_schedule": { "status": "live", "seconds_remaining": 284, "closed_early": false }
}
}
}Delete a cause
DELETE/v1/causes/mine/{uuid}
Removes a cause from your organisation.
- •A soft delete: the time people already gave to it is not destroyed, so your historic totals stay correct.
- •Close a cause instead if you only want it to stop accepting vibes.
Body / path parameters
uuiduuidrequired- The cause.
curl -X DELETE https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Cause deleted successfully."
}Organisation
Who a key speaks for, and who belongs to it.
Check a key
GET/v1/me
Confirms a key is live and tells you which organisation it belongs to. The cheapest way to verify your setup.
curl https://vibe-api.onaotc.com/api/v1/me \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Authenticated.",
"data": {
"organisation": {
"uuid": "69f9fa05-7097-41b2-9f7a-ca1ba6451b88",
"reference": "ORG-000001",
"name": "Helping Hands Foundation"
},
"api_key": { "reference": "AKY-000001", "name": "Marketing site" }
}
}List members
GET/v1/members
The people who belong to your organisation on Vibe — staff, not the end users of your own product.
- •Narrower than what your portal shows: an integration gets names, roles and contribution figures, never emails, phone numbers or dates of birth.
- •
roleis 1 for an admin, 2 for a member, 3 for the organisation owner.
curl https://vibe-api.onaotc.com/api/v1/members \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"data": {
"members": [
{
"reference": "USR-000004",
"first_name": "Asha",
"last_name": "Verma",
"profile_image": "/uploads/profiles/….png",
"role": 2,
"status": "active",
"joined_at": "2026-08-06T12:49:46.777Z",
"vibe_stats": { "causes": 10, "total_seconds": 89, "avg_seconds": 9 }
}
]
}
}End users
The people who vibe through your product. Vibe knows them only by the id you send — no email, no name beyond an optional label.
Retrieve a user’s impact
GET/v1/users/{user_id}/stats
What one of your users has contributed, and to which causes.
- •Returns 404 until that user has recorded their first vibe — there is nothing to create beforehand.
Body / path parameters
user_idstringrequired- Your own identifier for the person, as it was sent when their vibe was recorded.
curl https://vibe-api.onaotc.com/api/v1/users/USER_ID/stats \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"data": {
"user": { "user_id": "u_67890", "display_name": "Asha V", "created_at": "…" },
"stats": { "causes_vibed": 3, "total_seconds": 128, "avg_seconds": 43 },
"vibes": [
{ "cause_uuid": "…", "cause_title": "Education for All", "seconds": 40, "created_at": "…" }
]
}
}List end users
GET/v1/users
Everyone you have recorded a vibe for, newest first.
Query parameters
pageintegeroptional- Defaults to 1.
limitintegeroptional- 1–100. Defaults to 20.
curl "https://vibe-api.onaotc.com/api/v1/users?page=1&limit=20" \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"data": {
"users": [
{ "user_id": "u_67890", "display_name": "Asha V", "causes_vibed": 3, "created_at": "…" }
],
"meta": { "page": 1, "limit": 20, "total": 1, "total_pages": 1 }
}
}Organisation admin
The rest of what an owner does in this portal, available to a key. Creating and moderating causes has its own sections above.
Invite a member
POST/v1/invites
Emails an invitation to join your organisation on Vibe. Useful for inviting straight from your own HR system or intranet.
- •They join as a member (
role2), the same as an invite sent from this portal. A key cannot create administrators. - •The invite expires after three days. Sending a second one to the same address while the first is still pending is refused — wait for it to expire, or resend it from this portal.
- •Nothing is created on Vibe until they accept — an invite is not a member.
- •All three fields are required; there are no optional ones here.
Body / path parameters
first_namestringrequired- Their given name.
last_namestringrequired- Their family name.
emailstringrequired- Where the invitation is sent.
curl -X POST https://vibe-api.onaotc.com/api/v1/invites \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "first_name": "Asha", "last_name": "Verma", "email": "asha.verma@example.com" }'{
"success": true,
"message": "Invitation sent successfully.",
"data": {
"invite": {
"uuid": "4a2c4dc8-e497-498a-a357-77f2e1651637",
"email": "asha.verma@example.com",
"first_name": "Asha",
"last_name": "Verma",
"role": 2,
"status": "pending",
"expires_at": "2026-09-04T09:10:41.546Z",
"accepted_at": null,
"created_at": "2026-09-01T09:10:41.546Z"
}
}
}Update branding and permissions
PATCH/v1/organisation
Changes your logo, colours, bio, and who is allowed to post causes. Send only the fields you are changing.
- •At least one field is required, and every field is optional — the sample opposite shows all of them. Anything you leave out keeps its current value.
- •A logo is a file, so upload it first and send the returned path as
logo_url. - •Colours must be hex, including the
#. They set the accent your members see in the Vibe app. - •
cause_post_permissionis 1 for admins only, 2 for any member. - •The organisation you read back is narrower than the one this portal shows: an API key never receives your contact details, your owner’s email or phone, or your subscription.
Body / path parameters
logo_urlstringoptional- A path returned by the upload endpoint below. An absolute URL is refused; an empty string clears the logo.
theme_primary_colorstringoptional- Hex, e.g.
#e63946. theme_secondary_colorstringoptional- Hex, e.g.
#10b981. biostringoptional- Up to 300 characters, shown on your page in the app.
cause_post_permissionintegeroptional- 1 = admins only, 2 = any member.
curl -X PATCH https://vibe-api.onaotc.com/api/v1/organisation \
-H "X-API-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "bio": "We give our time to the causes near us.", "logo_url": "/uploads/org-logos/1788253610892-8eb73d49432e331f.png", "theme_primary_color": "#e63946", "theme_secondary_color": "#10b981", "cause_post_permission": 1 }'{
"success": true,
"message": "Organisation updated successfully.",
"data": {
"organisation": {
"uuid": "69f9fa05-7097-41b2-9f7a-ca1ba6451b88",
"reference": "ORG-000001",
"name": "Helping Hands Foundation",
"bio": "We give our time to the causes near us.",
"website_url": null,
"logo_url": "/uploads/org-logos/1788253610892-8eb73d49432e331f.png",
"theme_primary_color": "#e63946",
"theme_secondary_color": "#10b981",
"city": "Mumbai",
"state": null,
"cause_post_permission": 1,
"auto_approve_causes": false,
"status": "active",
"onboarded": true,
"created_at": "2026-08-06T12:20:08.472Z",
"updated_at": "2026-09-01T09:12:29.691Z"
}
}
}Managing people
Track the invitations you have sent, and remove someone who has left.
List invitations
GET/v1/invites
Every invitation your organisation has sent, and what became of it. Sending one is of little use without this.
- •
statusispending,accepted,revokedorexpired. Filter to one with the query parameter. - •
accepted_useris filled in once someone joins — that is how you tell an invitation from a member.
Query parameters
statusstringoptionalpending,accepted,revokedorexpired.
curl https://vibe-api.onaotc.com/api/v1/invites?status=pending \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Invitations fetched successfully.",
"data": {
"invites": [
{
"uuid": "01628846-ce1e-492c-865a-9c9a4bb41751",
"email": "asha.verma@example.com",
"first_name": "Asha",
"last_name": "Verma",
"role": 2,
"status": "pending",
"expires_at": "2026-09-04T09:30:36.578Z",
"accepted_at": null,
"accepted_user": null,
"created_at": "2026-09-01T09:30:36.578Z"
}
]
}
}Resend an invitation
POST/v1/invites/{uuid}/resend
Sends the invitation email again and extends the expiry by another three days.
Body / path parameters
uuiduuidrequired- The invitation.
curl -X POST https://vibe-api.onaotc.com/api/v1/invites/INVITE_UUID/resend \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Invitation resent successfully.",
"data": {
"invite": {
"uuid": "01628846-…",
"email": "asha.verma@example.com",
"status": "pending",
"expires_at": "2026-09-07T09:31:02.114Z"
}
}
}Revoke an invitation
DELETE/v1/invites/{uuid}
Cancels an invitation that has not been accepted. The link stops working immediately.
- •Revoking one that is already revoked or accepted returns an error rather than doing nothing quietly.
Body / path parameters
uuiduuidrequired- The invitation.
curl -X DELETE https://vibe-api.onaotc.com/api/v1/invites/INVITE_UUID \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Invitation revoked successfully.",
"data": { "invite": { "uuid": "01628846-…", "status": "revoked" } }
}Remove a member
DELETE/v1/members/{uuid}
Takes someone out of your organisation — the call to make when your own HR system offboards them.
- •Members only. An administrator cannot be removed through a key, so an integration can never lock your own admins out of the portal.
- •Their Vibe account survives; it simply stops belonging to your organisation, and the time they already gave stays counted.
- •Take the
uuidfromGET /v1/members.
Body / path parameters
uuiduuidrequired- The member.
curl -X DELETE https://vibe-api.onaotc.com/api/v1/members/MEMBER_UUID \ -H "X-API-Key: $VIBE_API_KEY"
{
"success": true,
"message": "Member removed from the organisation."
}Not available yet
Webhooks. Listed on the plans, but not implemented — no events are delivered. Poll /v1/causes for now.