Authentication
A key for your own company; OAuth 2.0 for an app that other companies use.
Two ways in
| API key | OAuth 2.0 | |
|---|---|---|
| For | Your own software, your own company | A third-party app a customer authorizes |
| Obtained | In the app, Developer → API keys | Through the authorization flow below |
| Token | aim_live_…, no expiry | aim_at_… 60 minutes, renewed with aim_rt_… 30 days |
| Sensitive scopes | Granted at creation | Third-party apps need AIMERICA's approval |
| Acts as | The person who created the key | The person who clicked Allow (or the company, with client_credentials) |
Either way the token travels in the Authorization: Bearer … header. A missing, unknown, expired or revoked token answers 401 unauthorized; a valid token without the scope a route needs answers 403 insufficient_scope with a WWW-Authenticate header naming the missing scope.
OAuth 2.0: authorization code + PKCE
AIMERICA is the authorization server. Register your app in the app (Developer → Apps): you get a client_id (aim_app_…), for a confidential app a client_secret shown once, and you declare your exact redirect URIs and the scopes the app may ask for. A public app (mobile, browser) has no secret and must use PKCE.
Your app The person's browser AIMERICA
─────── ──────────────────── ────────
1. make code_verifier,
code_challenge = S256(verifier)
2. send them to /oauth/authorize ─────────► a1merica.ai/voip/authorize
(sign in if needed, read the scopes,
Allow or Deny)
3. ◄──── 302 redirect_uri?code=…&state=… ────────────
4. POST /oauth/token (code + code_verifier) ─────────────────────────►
5. ◄──── access_token (60 min) + refresh_token (30 d)
6. Authorization: Bearer aim_at_… on every /v1 request
7. POST /oauth/token grant_type=refresh_token when expires_in runs out
1. Send the person to authorize
# Python: build the PKCE pair
import base64, hashlib, secrets
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
GET https://api.a1merica.ai/oauth/authorize
?response_type=code
&client_id=aim_app_1a2b3c4d5e6f
&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
&scope=users%3Aread%20calls%3Aread
&state=xyz
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
redirect_uri must match one of the registered addresses exactly; the requested scopes must be among the app's; state comes back untouched. The person signs in if needed, sees the app, its scopes as plain sentences and the company involved, then Allow or Deny. On deny: redirect_uri?error=access_denied&state=….
2. Exchange the code
The code is good for 10 minutes and one use. The body is form-encoded (RFC 6749).
POST https://api.a1merica.ai/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=…
&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
&client_id=aim_app_1a2b3c4d5e6f
&code_verifier=… # public apps: PKCE instead of a secret
# confidential apps add client_secret=… (or HTTP Basic client_id:client_secret)
{
"access_token": "aim_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "aim_rt_…",
"refresh_token_expires_in": 2592000,
"scope": "users:read calls:read",
"org_id": "a1b2…",
"user_id": "9d8c…"
}
3. Refresh
POST https://api.a1merica.ai/oauth/token
grant_type=refresh_token&refresh_token=aim_rt_…&client_id=aim_app_…
A refresh rotates the pair: new access token, new refresh token. The old refresh token dies as soon as the new access token is first used, or 60 seconds later — enough to survive a network that drops between the answer and your saving it.
Client credentials (no person)
A confidential app can act for the company that registered it, with nobody behind it: grant_type=client_credentials. The token has no user_id; routes that speak of "me" answer 403.
POST https://api.a1merica.ai/oauth/token
grant_type=client_credentials&scope=calls%3Aread&client_id=aim_app_…&client_secret=…
Lifetimes and revocation
| Token | Prefix | Lifetime | How it dies |
|---|---|---|---|
| API key | aim_live_ | Until revoked | Revoked in the app (takes effect at once) |
| Authorization code | — | 10 minutes, single use | Used, or expired |
| Access token | aim_at_ | 60 minutes | Expires; POST /oauth/revoke; the person removes the app; the app is suspended |
| Refresh token | aim_rt_ | 30 days | Rotated on use; POST /oauth/revoke; the person removes the app |
POST https://api.a1merica.ai/oauth/revoke
token=aim_rt_…&client_id=aim_app_…
POST /oauth/revoke (RFC 7009) takes an access or a refresh token and answers 200 even when it is already dead. POST /v1/oauth/introspect (client-authenticated) says whether a token is active, its scopes, its company and its person. When a person removes an app in the app (Developer → Apps → grants), every token of theirs for that app stops at once: the next request answers 401.
The /oauth/token endpoint answers errors the RFC way — {"error": "invalid_grant", "error_description": "…"} — because OAuth libraries expect it; everywhere else it is the AIMERICA envelope.
HTTP/1.1 400 Bad Request
{"error": "invalid_grant", "error_description": "The code has expired or was already used"}
Never store a token or a key in clear in a repository or a log. We keep only SHA-256 hashes: a lost key cannot be recovered, only replaced.
