Authentication
Every endpoint takes a bearer token generated in Portal → your workspace → API Token (left-hand menu).
Authorization: Bearer {api_token}401 and 403 mean different things
Section titled “401 and 403 mean different things”The API distinguishes two failures in a way that is easy to get backwards:
| Situation | Status |
|---|---|
| Header present, token invalid or revoked | 401 Unauthorized |
| Header absent entirely | 403 Forbidden |
{ "error": { "status_code": 401, "message": "Unauthorized", "detail": "No permission -- see authorization schemes" }}{ "error": { "status_code": 403, "message": "Forbidden", "detail": "Request forbidden -- authorization will not help" }}A GET on a POST-only endpoint also returns 403, not 405. No endpoint returns 405.
What the slug gives access to
Section titled “What the slug gives access to”Get application info is served without authentication — but only while the application is still open. The hosted form needs this to fetch its own schema, and it is what lets an applicant who dropped off return to the same link and finish.
# No Authorization header. 200 while the application is unfinished.curl 'https://app.uppass.io/en/api/forms/{form_slug}/applied-forms/{slug}/info/'| Application state | Unauthenticated GET .../info/ |
|---|---|
| Created, not yet submitted | Returns the application |
| Submitted | Requires the API token |
| Deleted | 404 Not Found — permanently removed |
So the exposure is bounded: a slug reaches an in-progress form and nothing else. Results, document images and screening outcomes are never readable this way — those come from Get application result, which always enforces the token.
| Endpoint | Token required |
|---|---|
POST .../create/ |
Yes |
GET .../result/ |
Yes |
POST .../submit/ |
Yes |
POST .../hook-submit/ |
Yes |
GET .../info/ |
Only once submitted. A deleted application is 404 for everyone. |
Verifying inbound webhooks
Section titled “Verifying inbound webhooks”Webhooks carry the Bearer secret you configured in Portal → Connect → Add Webhook. Rejecting a mismatch is the only thing authenticating the caller.
Authorization: Bearer {webhook_secret}Compare the header against the secret you stored, and answer 401 on a mismatch. There
is no signature header; this comparison is the whole check.
See Webhooks for the rest of the receiver contract.