OAuth
Let your app act on a usend organization with OAuth 2.0.
Use OAuth when an app you build needs to act on other people's usend organizations without asking them to paste an API key. See Building an OAuth client for a walkthrough.
usend supports public clients only (no client secret), the authorization code flow with mandatory PKCE (S256), and refresh tokens. Discovery metadata is published at https://api.usend.et/.well-known/oauth-authorization-server.
All token, register and revoke endpoints accept application/x-www-form-urlencoded or JSON, allow requests from any origin (no cookies are involved), and answer errors as { "error": "...", "error_description": "..." }.
Scopes
| Scope | Allows |
|---|---|
sending_access | Send emails (POST /emails, POST /emails/batch). |
full_access | Everything an API key with full access can do, except creating API keys. Only organization owners and admins can grant it. |
A token acts for exactly one organization: the one the user chose on the consent screen.
Register a client
POST /oauth/register
| Name | Type | Required | Description |
|---|---|---|---|
client_name | string | Yes | Shown to users on the consent screen, up to 100 characters. |
redirect_uris | string[] | Yes | 1 to 10 URIs. Must be https, or http on localhost, 127.0.0.1 or [::1]. No fragments. Matched exactly. |
token_endpoint_auth_method | string | No | Only none. |
curl -X POST https://api.usend.et/oauth/register \
-H "Content-Type: application/json" \
-d '{ "client_name": "Acme Mailer", "redirect_uris": ["https://acme.example/callback"] }'{
"client_id": "usend_Qm3x...",
"client_id_issued_at": 1760000000,
"client_name": "Acme Mailer",
"redirect_uris": ["https://acme.example/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"scope": "full_access sending_access"
}Authorize
Send the user's browser to https://app.usend.et/oauth/authorize with these query parameters.
| Name | Required | Description |
|---|---|---|
response_type | Yes | code. |
client_id | Yes | From registration. |
redirect_uri | Yes | One of the registered URIs, exactly. |
scope | Yes | Space-separated scopes. |
code_challenge | Yes | Base64url SHA-256 of your code_verifier. |
code_challenge_method | Yes | S256. |
state | Recommended | Returned unchanged. Check it. |
The user signs in, picks the organization and approves. usend redirects to your redirect_uri with code, state and iss, or with error=access_denied. A request with an unknown client or a redirect URI that does not match is never redirected: the user sees an error instead.
Exchange the code
POST /oauth/token
| Name | Description |
|---|---|
grant_type | authorization_code. |
client_id | Your client ID. |
code | The authorization code. Valid for 60 seconds, single use. |
code_verifier | The PKCE verifier (43 to 128 characters). |
redirect_uri | The same URI as in the authorization request. |
{
"access_token": "usa_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "usr_...",
"scope": "full_access"
}Use the access token like an API key: Authorization: Bearer usa_....
Refresh a token
POST /oauth/token with grant_type=refresh_token, client_id and refresh_token. The response has the same shape and a new refresh token. Refresh tokens are single use: store the new one and discard the old one. If a refresh token is used twice, usend revokes the whole grant and your app must ask the user to authorize again.
Revoke a token
POST /oauth/revoke with token and optionally client_id. Always answers 200. Revoking a refresh token ends the grant; revoking an access token removes that token only.
Connected apps
Users see and revoke the apps they connected in Settings under Connected apps, or with the session-only endpoints GET /oauth/grants and DELETE /oauth/grants/{id}.
Errors
error | Meaning |
|---|---|
invalid_request | A parameter is missing or malformed. |
invalid_client | Unknown client_id. |
invalid_grant | The code or refresh token is wrong, expired, used, or its grant was revoked; or the verifier or redirect URI does not match. |
unsupported_grant_type | Use authorization_code or refresh_token. |
invalid_redirect_uri / invalid_client_metadata | Registration was refused. |
rate_limit_exceeded | Too many requests. Retry after the Retry-After header. |