AIMERICAAPI · Developers

Authentication

A key for your own company; OAuth 2.0 for an app that other companies use.

Two ways in

API keyOAuth 2.0
ForYour own software, your own companyA third-party app a customer authorizes
ObtainedIn the app, Developer → API keysThrough the authorization flow below
Tokenaim_live_…, no expiryaim_at_… 60 minutes, renewed with aim_rt_… 30 days
Sensitive scopesGranted at creationThird-party apps need AIMERICA's approval
Acts asThe person who created the keyThe 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

TokenPrefixLifetimeHow it dies
API keyaim_live_Until revokedRevoked in the app (takes effect at once)
Authorization code—10 minutes, single useUsed, or expired
Access tokenaim_at_60 minutesExpires; POST /oauth/revoke; the person removes the app; the app is suspended
Refresh tokenaim_rt_30 daysRotated 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.