Vibe logovibe

API reference

Everything the Vibe integration API exposes. Every response below was captured from the live API — nothing here is illustrative.

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.

Base URL
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.

Request
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.

  1. 1Pick a category — or name a new oneGET /v1/categories returns the active categories; send the uuid you want as category_uuid. If none fits, skip this call and send category_name instead — an existing name is reused (case-insensitively) and a new one is created. Never send both: the name wins and the uuid is ignored.
  2. 2Upload the cover image — optionalPOST /v1/uploads/image as multipart, with the file in file and folder=causes. It stores the file and returns a path; it does not attach it to anything. Keep the url for the next step. A cause is perfectly valid with no cover, so skip this if you have no image.
  3. 3Create the causePOST /v1/causes with the title, the category from step 1, the url from step 2 as image_url, and the Vibe schedule — open_at (with a timezone offset) and duration_seconds, both required. Add the repeat fields if the window should come back around.
  4. 4Read it back from your own listGET /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 on moderation_status.
All five steps
# 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.
422 Response
{
  "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.

429 Response
{
  "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_at and closes on its own once duration_seconds have 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_at must carry a timezone offset (Z or ±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 with custom. 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_until or repeat_count, never both and never neither. An open-ended series nobody remembers to close runs for ever.
  • •repeat_days belongs to custom alone, and occurrence one is the open date — so open_at has to fall on one of the days you chose, or the series would begin off its own rule. Weekdays are 0 = Sunday … 6 = Saturday.
  • •monthly and yearly clamp 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. Its status — scheduled, live or closed — is computed on every read, so a Vibe closes on its own: nothing has to run, and there is no callback to wait for. Read status, not lifecycle_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_public reaches 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: approved means it has cleared review, pending means 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_at misses 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_uuid from GET /v1/categories to file the cause under an existing category, or send category_name as free text.
  • •category_name is 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/categories for everyone immediately, and the name is screened for abuse. Prefer category_uuid from 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_name wins and category_uuid is 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 the uuid you would reuse next time.
  • •A cover image is two calls, in this order: upload the file (next endpoint), then send the url it returns back here as image_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 title and a category are required — send as few of the rest as you like.
  • •goal is stored but not echoed here: this response is the public shape of a cause. Read it back from GET /v1/causes/mine to 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_frequencyenumoptional
none (default), daily, weekly, monthly, yearly or custom. Omit for a one-off.
repeat_daysinteger[]optional
custom only. Weekdays the window opens on, 0 = Sunday … 6 = Saturday.
repeat_untilstringoptional
YYYY-MM-DD. The series runs to the end of this day. Use this OR repeat_count.
repeat_countintegeroptional
Total occurrences, 2–366. Use this OR repeat_until.
category_uuiduuidoptional
An existing category, from GET /v1/categories. One of this or category_name is 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_uuid if 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.
Request
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" }'
Response
{
  "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 url to the endpoint that should use it, as image_url on a cause or logo_url on your organisation. An upload nobody references is simply an orphaned file.
  • •folder is causes for a cover image or org-logos for a logo. Anything else — including omitting it — files the image under misc. 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_url and logo_url only 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 url you 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.
folderstringoptional
causes for a cover image, org-logos for a logo. Anything else lands in misc.
Request
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"
Response
{
  "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 uuid from here and send it as category_uuid when 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_name nobody has used yet is what adds one.
  • •reference (CAT-0001) is for showing a person; uuid is what the API takes. Unpaged by default — pass limit to page, and search to 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.
Request
curl "https://vibe-api.onaotc.com/api/v1/categories?search=water&page=1&limit=100" \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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) and lifecycle_status (active, closed).
  • •stats is 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=scheduled includes one that is merely between occurrences, and closed only ever means the series itself is finished. repeat.occurrence / repeat.total_occurrences say 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_statusstringoptional
pending, approved or rejected.
lifecycle_statusstringoptional
active or closed. The manual hold, not the schedule.
vibe_statusenumoptional
live, scheduled or closed. 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_datestringoptional
YYYY-MM-DD. Created on or after.
end_datestringoptional
YYYY-MM-DD. Created on or before.
pageintegeroptional
Defaults to 1.
limitintegeroptional
1–100. Defaults to 20.
Request
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"
Response
{
  "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.
  • •vibes names each contribution by reference and seconds — no names or emails, and source says whether it came from the Vibe app or through your integration.

Body / path parameters

uuiduuidrequired
The cause.
Request
curl https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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_at and duration_seconds may be sent alone — send duration_seconds by itself to extend or shorten a Vibe that is already running, and the stored open_at is 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_frequency alone 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 custom drops 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_uuid is 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 none to stop repeating.
repeat_daysinteger[]optional
custom only. 0 = Sunday … 6 = Saturday.
repeat_untilstringoptional
YYYY-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.
Request
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" }'
Response
{
  "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 pending can be approved — anything else is a 400.
  • •Approving does not move the schedule. A cause reviewed after its open_at has passed misses that window — on a one-off it needs rescheduling, and on a repeating one it simply starts from the next occurrence. Check vibe_schedule.status in 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.
Request
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/approve \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.

  • •reason is required, 3–500 characters. It is shown to the submitter, so write it for them.
  • •Only a cause still pending can be rejected.

Body / path parameters

reasonstringrequired
Why it was declined. Shown to the submitter.
Request
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." }'
Response
{
  "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 closed even while its window is still open, and vibe_schedule.closed_early is 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.
Request
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/close \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.status in the response rather than assuming: live means it is taking vibes again, scheduled that it will, closed that it needs rescheduling.

Body / path parameters

uuiduuidrequired
The closed cause.
Request
curl -X POST https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID/reactivate \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
Request
curl -X DELETE https://vibe-api.onaotc.com/api/v1/causes/mine/CAUSE_UUID \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.

Request
curl https://vibe-api.onaotc.com/api/v1/me \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
  • •role is 1 for an admin, 2 for a member, 3 for the organisation owner.
Request
curl https://vibe-api.onaotc.com/api/v1/members \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
Request
curl https://vibe-api.onaotc.com/api/v1/users/USER_ID/stats \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
Request
curl "https://vibe-api.onaotc.com/api/v1/users?page=1&limit=20" \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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 (role 2), 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.
Request
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" }'
Response
{
  "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_permission is 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.
Request
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 }'
Response
{
  "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.

  • •status is pending, accepted, revoked or expired. Filter to one with the query parameter.
  • •accepted_user is filled in once someone joins — that is how you tell an invitation from a member.

Query parameters

statusstringoptional
pending, accepted, revoked or expired.
Request
curl https://vibe-api.onaotc.com/api/v1/invites?status=pending \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
Request
curl -X POST https://vibe-api.onaotc.com/api/v1/invites/INVITE_UUID/resend \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.
Request
curl -X DELETE https://vibe-api.onaotc.com/api/v1/invites/INVITE_UUID \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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 uuid from GET /v1/members.

Body / path parameters

uuiduuidrequired
The member.
Request
curl -X DELETE https://vibe-api.onaotc.com/api/v1/members/MEMBER_UUID \
  -H "X-API-Key: $VIBE_API_KEY"
Response
{
  "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.