{
  "info": {
    "name": "Campaign API",
    "description": "Connect your product: invite people, report outcomes, and hear by webhook which are verified, then paid after the deadline.\n\nThis 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.\n\nLimits. Writes: 120 a minute per organization. Reads: 600 a minute per organization; a load code spends one. Before an API key is read: 600 a minute per address. The campaign read and the four certificate reads that name one certificate: 120 a minute per address. The signing key, the issuer document and the revocation list: none. Every 429 carries retry_after and a Retry-After header with the same seconds.\n\nErrors. Branch on code. message is a sentence for a person and may change. details carries the facts its code names.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "With your API key",
      "item": [
        {
          "name": "Report one outcome",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{externalId}}",
                "disabled": true
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/outcomes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "outcomes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"campaign_id\": \"{{campaignId}}\",\n  \"email\": \"ada@example.com\",\n  \"reference\": \"course-solidity-101\",\n  \"occurred_at\": \"2026-09-11T14:03:22Z\",\n  \"external_id\": \"{{externalId}}\",\n  \"kind\": \"completed\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Answers 202 for any well-formed request the API key may make. A fact about the data comes back as a reason, never as an error. Idempotency-Key is accepted as a header and must equal external_id where both are sent. They disagree, 422."
          }
        },
        {
          "name": "Read one outcome back",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/outcomes/{{externalId}}?campaign_id={{campaignId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "outcomes",
                "{{externalId}}"
              ],
              "query": [
                {
                  "key": "campaign_id",
                  "value": "{{campaignId}}"
                }
              ]
            },
            "description": "The state, the reward in human units beside base units, and the certificate where the holder’s passport shows it."
          }
        },
        {
          "name": "Invite one person",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{externalId}}",
                "disabled": true
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/invitations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "invitations"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"campaign_id\": \"{{campaignId}}\",\n  \"email\": \"ada@example.com\",\n  \"external_id\": \"{{externalId}}\",\n  \"send\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Answers 202 with the invitation as it stands and a reason, for any request the API key may make. An address already on the list is a reason, never an error. Idempotency-Key is accepted as a header and must equal external_id where both are sent. They disagree, 422."
          }
        },
        {
          "name": "Read one invitation back",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/invitations/{{externalId}}?campaign_id={{campaignId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "invitations",
                "{{externalId}}"
              ],
              "query": [
                {
                  "key": "campaign_id",
                  "value": "{{campaignId}}"
                }
              ]
            },
            "description": "The invitation as it stands, by your own id, with the campaign named."
          }
        },
        {
          "name": "Mint a load code for the status card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{externalId}}",
                "disabled": true
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/embed-codes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "embed-codes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"campaign_id\": \"{{campaignId}}\",\n  \"email\": \"ada@example.com\",\n  \"origin\": \"https://learn.example.ch\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "One person’s own outcome, read-only. This is the one widget with a server behind it. Your server mints a single-use load code, the loader posts it into the frame, and the frame draws from that code alone. The code never appears in a URL and the frame never holds a session. It answers 200 with a code for every well-formed address, whether or not that person holds a place. The frame decides which card to draw and draws both at one height, so the call never tells you who joined. Each code is a read of your door. It spends one of your 600 reads a minute and writes one row on your Integrations page. The mint refuses a request that carries a browser Origin. The API key belongs on your server: an API key in a bundle is an API key you have published. A partner with no server cannot ship this widget; the join card and the badge need none."
          }
        }
      ]
    },
    {
      "name": "Public reads",
      "auth": {
        "type": "noauth"
      },
      "item": [
        {
          "name": "Read a campaign’s public facts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/campaigns/{{campaignId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "campaigns",
                "{{campaignId}}"
              ]
            },
            "description": "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. 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."
          }
        },
        {
          "name": "Read a certificate’s signed record",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/{{credentialId}}/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "{{credentialId}}",
                "verify"
              ]
            },
            "description": "The signed record, as application/vc+ld+json. The certificate’s own address answers the same document to a request whose Accept names JSON. The certificate’s own address, /p/{handle}/{id}, answers the same document to a request whose Accept names JSON."
          }
        },
        {
          "name": "Read a certificate’s exact text",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/{{credentialId}}/document",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "{{credentialId}}",
                "document"
              ]
            },
            "description": "The exact text, as written when the certificate was issued. Hash the bytes as served; never parse and re-serialize them first."
          }
        },
        {
          "name": "Read a certificate’s public record",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/{{credentialId}}/chain",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "{{credentialId}}",
                "chain"
              ]
            },
            "description": "Its public record, read when you ask and kept a minute, unsigned: status is matches, revoked, does_not_match, revocation_pending or unreadable, with checked_at. Only once the entry answers the certificate’s record number and Campaign’s issuer does it say more than unreadable. An entry at the certificate’s own address also carries chain_id, registry, record_number, token_id and tx_hash."
          }
        },
        {
          "name": "Read the signing key",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/signing-key",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "signing-key"
              ]
            },
            "description": "The public key every proof names, as a Multikey. Its controller, Campaign’s own address, answers a request whose Accept names JSON with its controller document, which lists the key."
          }
        },
        {
          "name": "Read the issuer’s controller document",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/issuer",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "issuer"
              ]
            },
            "description": "The issuer’s controller document. The origin’s own / answers the same document to a request whose Accept names application/json or application/ld+json; a browser gets the homepage."
          }
        },
        {
          "name": "Read the revocation list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/credentials/status/1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "credentials",
                "status",
                "1"
              ]
            },
            "description": "The revocation list, signed by Campaign: one bit for each certificate, 1 once it is revoked. A copy of a record you were given still finds its bit after its holder hides the certificate."
          }
        },
        {
          "name": "Read the exact text by its record number",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/r/{{recordNumber}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "r",
                "{{recordNumber}}"
              ]
            },
            "description": "The address a certificate’s entry on the public record names: its version 3 exact text, byte for byte, behind the same rule as the document read."
          }
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://campaign.udao.org",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "campaignId",
      "value": "",
      "type": "string"
    },
    {
      "key": "externalId",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialId",
      "value": "",
      "type": "string"
    },
    {
      "key": "recordNumber",
      "value": "",
      "type": "string"
    }
  ]
}
