# Developers

The calls your app makes to report an outcome, invite a person, and read each back.

Connect your product: invite people, report outcomes, and hear by [webhook](https://campaign.udao.org/developers/webhooks#setting-one-up) which are verified, then paid after the deadline.

- [OpenAPI 3.1](https://campaign.udao.org/developers/openapi.json)
- [Postman collection](https://campaign.udao.org/developers/campaign.postman_collection.json)
- [Example in Node](https://campaign.udao.org/developers/quickstart#the-example)

**Your API key:** an owner or a developer creates it in [your dashboard](https://campaign.udao.org/console/organization/integrations), under Integrations. [Quickstart](https://campaign.udao.org/developers/quickstart#create-an-api-key)

1. You invite, or share the join link; people join themselves [`POST /api/v1/invitations`](https://campaign.udao.org/developers/invitations#invite-one-person) [`invitation.joined`](https://campaign.udao.org/developers/webhooks#the-events)
2. Your app reports an outcome [`POST /api/v1/outcomes`](https://campaign.udao.org/developers/outcomes#report-an-outcome)
3. Your organization’s verifier decides, or auto-verify on a trial campaign [`outcome.verified`](https://campaign.udao.org/developers/webhooks#the-events) [`outcome.rejected`](https://campaign.udao.org/developers/webhooks#the-events)
4. Your organization pays the person after the deadline [`outcome.paid`](https://campaign.udao.org/developers/webhooks#the-events)
5. The person keeps a certificate, checkable once they publish it [Certificates](https://campaign.udao.org/developers/certificates)

- Read back [`GET /api/v1/outcomes/{external_id}`](https://campaign.udao.org/developers/outcomes#read-an-outcome) [`GET /api/v1/invitations/{external_id}`](https://campaign.udao.org/developers/invitations#read-an-invitation)
- Show cards on your pages [`POST /api/v1/embed-codes`](https://campaign.udao.org/developers/embeds#the-status-card)

## Report an outcome

`POST https://campaign.udao.org/api/v1/outcomes`

Report one when a person finishes. It is kept against their place with a hash of what you sent, and the same external_id twice answers the first reply again. An outcome for someone who has not joined yet is kept as a record. Reporting moves no money: your organization pays each reward from its own account.

**curl**

```bash
curl https://campaign.udao.org/api/v1/outcomes \
  -H "Authorization: Bearer camp_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
      "campaign_id": "8f2c6a1e-4b7d-4e39-a5c0-3d9e1f6b2a74",
      "email": "ada@example.com",
      "reference": "course-solidity-101",
      "occurred_at": "2026-09-11T14:03:22Z",
      "external_id": "your-own-id-8f3a",
      "kind": "completed"
    }'
```

**Node**

```js
const answer = await fetch("https://campaign.udao.org/api/v1/outcomes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CAMPAIGN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "campaign_id": "8f2c6a1e-4b7d-4e39-a5c0-3d9e1f6b2a74",
    "email": "ada@example.com",
    "reference": "course-solidity-101",
    "occurred_at": "2026-09-11T14:03:22Z",
    "external_id": "your-own-id-8f3a",
    "kind": "completed"
  }),
});
console.log(answer.status, await answer.json());
```

**Python**

```python
import os
import requests

answer = requests.post(
    "https://campaign.udao.org/api/v1/outcomes",
    headers={"Authorization": "Bearer " + os.environ["CAMPAIGN_API_KEY"]},
    json={
        "campaign_id": "8f2c6a1e-4b7d-4e39-a5c0-3d9e1f6b2a74",
        "email": "ada@example.com",
        "reference": "course-solidity-101",
        "occurred_at": "2026-09-11T14:03:22Z",
        "external_id": "your-own-id-8f3a",
        "kind": "completed"
    },
    timeout=10,
)
print(answer.status_code, answer.json())
```

**202 Accepted**

```json
{
  "external_id": "your-own-id-8f3a",
  "campaign_id": "8f2c6a1e-4b7d-4e39-a5c0-3d9e1f6b2a74",
  "status": "pending",
  "reason": "awaiting_review",
  "received_at": "2026-09-11T14:03:25Z",
  "outcome_url": "https://campaign.udao.org/api/v1/outcomes/your-own-id-8f3a?campaign_id=8f2c6a1e-4b7d-4e39-a5c0-3d9e1f6b2a74"
}
```

## Authorization

Every call carries one header, your API key, and nothing else. An owner or a developer creates it, and needs a sign-in from the last 10 minutes. It is shown once; keep it on your server, never in a browser. Your organization keeps at most 10 live.

```http
Authorization: Bearer camp_sk_live_...
```

An API key for trial campaigns only begins camp_sk_trial_ and writes to trial campaigns alone. Trial campaigns are the test mode: the loop runs whole and every figure comes back with simulated true.

The base address

```text
https://campaign.udao.org
```

## Public reads

- `GET /api/public/campaigns/{id}`: No API key. It answers a campaign’s public facts: its title, reward, places, dates and who funds it, up to 120 requests a minute from one address.
- **The certificate reads**: No API key either. [Certificates](https://campaign.udao.org/developers/certificates#endpoints)

These get the same 404 campaign_not_found:

- a campaign before it goes live or after it is closed or canceled
- an id nobody holds
- while Udao has not approved the organization, any reader but its signed-in members, the people it invited and the people holding a place

## Versions and changes

This is version 1, in the path. We only add to it: new endpoints, new optional fields, new fields in answers and webhooks, new reasons and codes. Ignore what you do not know. Anything that would break a client comes as version 2, beside version 1, announced here at least 30 days ahead.

- **2026-10-10**: Revoking a verified or paid outcome, or inviting, on a real campaign answers 403 power_not_granted unless an owner gave the API key that. Reading a real campaign’s outcome or invitation with an API key for trial campaigns only answers 409 trial_only_credential.
- **2026-10-05**: Invitations: POST /api/v1/invitations puts one person on a campaign’s list, its read answers invited, joined or removed, and invitation.joined says when they join.
- **2026-10-04**: The API published as one OpenAPI 3.1 document and a Postman collection. The read no longer lists proof, which it never sent.

[Read the quickstart](https://campaign.udao.org/developers/quickstart)
