# REST API

Exchange an authorization code and fetch the member fields approved for your app.

Source: https://zetapad.com/developers/docs/api-reference

## Base URL and authentication

The production API base URL is `https://zetapad.com`. Every app completes login through these endpoints from its backend over HTTPS, including with no scopes. Client credentials alone cannot access a member: first obtain that member's one-use code through iframe consent. Do not expose client secrets or bearer tokens to the iframe.

The [OpenAPI document](https://zetapad.com/developers/docs/openapi.json) describes login and [app call endpoints](https://zetapad.com/developers/docs/calls). The management dashboard uses separate member APIs.

## POST /v1/apps/token

Exchange a one-use authorization code obtained from iframe `authenticate`. Validate and consume the visitor's session-bound state challenge before making this call.

```http
POST https://zetapad.com/v1/apps/token
Authorization: Basic <base64(clientId:clientSecret)>
Content-Type: application/json

{ "code": "zp_code_..." }
```

The JSON body must contain only `code`, a nonempty string of at most 128 characters. The code is valid for 60 seconds and can be used once by the registered app that received it. Invalid credentials, codes, or ended access are rejected.

Successful response (`200`):

```json
{
  "access_token": "zp_token_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scopes": ["profile:read"]
}
```

Keep `access_token` in your backend's visitor session and record its expiry. There are no refresh tokens; use `authenticate` in the iframe to obtain another code.

## GET /v1/apps/userinfo

Read the verified shared Zetapad member ID and approved fields:

```http
GET https://zetapad.com/v1/apps/userinfo
Authorization: Bearer zp_token_...
```

Successful response (`200`) with `profile:read` only:

```json
{
  "userId": "zp_member_...",
  "scopes": ["profile:read"],
  "username": "member_01",
  "name": "Member",
  "countryCode": "IN"
}
```

`userId` and `scopes` are always included, including with `scopes: []`. `userId` is Zetapad's server-generated primary member ID, shared across apps. This endpoint supplies verified identity after credential exchange. Use the server-verified `userId` to key experiences across apps.

With no scopes, the complete response is:

```json
{ "userId": "zp_member_...", "scopes": [] }
```

`profile:read` adds `username`, `name`, and `countryCode`; `birthdate:read` adds `birthdate` in `YYYY-MM-DD` format. Unapproved fields are omitted. Every member has a username; name, country, and birthdate can be null. Usernames are unique, lowercase handles of 3–30 letters, numbers, or underscores and can change anytime. A released username can belong to another account. Use the permanent `userId` for saved data and the current `username` for display. See [permissions](https://zetapad.com/developers/docs/authentication#permissions-and-profile-fields).

Current consent and app access are checked on every userinfo read. Call it before protected app actions that rely on Zetapad access. On failure, stop the action and end the app session's Zetapad access.

Resolve identity from your server session rather than a `userId` in the request body. You may return approved UI fields to the browser after verification. Call scopes add a public call roster and hosted presentation after native per-call approval. App storage remains on your backend. Member and admin GraphQL APIs do not accept app credentials or app access tokens.

## GET /v1/apps/calls/status

Silently check for an accepted call in this app launch's owning campus tab before asking to use it. Requires completed backend login, `calls:read`, current app consent, and a valid native mount; no per-call approval is needed. Send the app's server-held bearer token, with no request body or selectors:

```http
GET https://zetapad.com/v1/apps/calls/status
Authorization: Bearer zp_token_...
```

Example response (`200`):

```json
{ "inCall": true, "canAttach": true, "access": "approvalRequired" }
```

`access` is `unavailable`, `approvalRequired`, or `approved`. An unavailable result has both booleans false; accepted participation has both true. Pending invitations and calls in other tabs are unavailable. The response contains only these three fields and creates no grant, prompt, or presentation. Invalid login, revoked consent, or stale mounts return errors rather than false availability.

Offer **Use current call in this app** when `canAttach` is true. On the member's choice, request `POST /v1/apps/calls/attach`; it independently rechecks access and requests native approval when needed. Remembered `approved` access still requires attachment and presentation preparation. See the [availability contract](https://zetapad.com/developers/docs/calls#silent-call-availability) and [tested example](https://zetapad.com/developers/docs/examples/calls#check-availability-without-prompting).

## Error responses

```json
{
  "error": {
    "code": "ACCESS_REVOKED",
    "message": "App access has ended. Authenticate again to continue."
  }
}
```

| HTTP status | Code | Meaning |
| --- | --- | --- |
| `400` | `BAD_USER_INPUT` | Invalid JSON token-exchange body or unsupported fields |
| `401` | `INVALID_CLIENT` | Client ID or secret could not be verified |
| `401` | `INVALID_TOKEN` | Code or access token is invalid, expired, or already used |
| `403` | `ACCESS_REVOKED` | Member consent or issued access ended |
| `403` | `APP_UNAVAILABLE` | The app can no longer be launched or used in this context |
| `403` | `FORBIDDEN` | A required call scope or permission is missing |
| `503` | `INTERNAL_SERVER_ERROR` | Temporary app-service failure |

Success and error responses use `Cache-Control: private, no-store`. Do not cache them or log credentials. For recovery steps, see [errors and troubleshooting](https://zetapad.com/developers/docs/troubleshooting).
