OAuth 2.0 Authentication

Use OAuth 2.0 when you are building a third-party integration that acts on
behalf of one or more Apptoto users. If you are writing a script that calls
the API as yourself, HTTP Basic Authentication
is simpler.

Register an OAuth Application

In the Apptoto portal, go to Settings > Integrations > OAuth Applications
and click Register OAuth Application. Fill in:

  • Name: a friendly name shown to users on the consent screen.
  • Redirect URI: the URL Apptoto sends the user back to after they approve
    the consent screen. Must match exactly when you start the flow.
  • Server-Side Application: leave on for apps that run on a server you
    control (Rails, Node, Python, etc.) and can keep a secret. Turn off for
    mobile, desktop, or single-page apps - they should use PKCE instead.

When you create the app, the Client ID and Client Secret are shown
once. Copy the secret immediately - if you lose it, delete the application
and register a new one.

Single-Tenant by Default

Newly registered OAuth applications are private to the user that registered
them. Only that user can complete the /oauth/authorize flow against the
app. This lets you build and test integrations privately without exposing
them to other Apptoto accounts.

When your integration is ready for general use, email
[email protected] to request publication.
Once published, any logged-in Apptoto user can authorize the app.

Authorization Code Flow

OAuth on Apptoto uses the standard authorization_code flow.

1. Send the user to the consent screen

Direct the user's browser to:

https://www.apptoto.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_REDIRECT_URI&
  response_type=code&
  scope=api

After the user clicks Authorize, Apptoto redirects to your
redirect_uri with a one-time code parameter:

https://yourapp.example.com/oauth/callback?code=ABC123

2. Exchange the code for an access token

curl -X POST https://api.apptoto.com/oauth/token \
  -d grant_type=authorization_code \
  -d code=ABC123 \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d redirect_uri=YOUR_REDIRECT_URI

Response:

{
  "access_token":  "...",
  "token_type":    "Bearer",
  "expires_in":    2592000,
  "refresh_token": "...",
  "scope":         "api",
  "created_at":    1747507200
}

3. Call the API with the access token

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  https://api.apptoto.com/v1/userinfo

Every /v1/* endpoint accepts OAuth Bearer tokens, including the ones
documented elsewhere in this reference.

4. Refresh an expired access token

Access tokens are valid for 30 days. When they expire, use the
refresh_token to mint a new one:

curl -X POST https://api.apptoto.com/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=YOUR_REFRESH_TOKEN \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET

Refresh tokens rotate - each refresh response contains a new
refresh_token that replaces the previous one.

Scopes

Apptoto uses granular per-domain scopes. Each scope grants access to
the endpoints listed below. Your application must declare every scope it
needs when you register it (Settings > Integrations > OAuth Applications),
and request the same scopes (or a subset) in the authorize URL.

Read scopes

ScopeEndpoints
calendars:readGET /v1/calendars
address_books:readGET /v1/address_books
contacts:readGET /v1/contact, GET /v1/contacts
events:readGET /v1/event, GET /v1/events
bookings:readGET /v1/availability, GET /v1/bookings, GET /v1/booking_pages
imports:readGET /v1/import
lists:readGET /v1/lists
messaging:readGET /v1/conversations, GET /v1/conversation_messages

Write scopes

ScopeEndpoints
contacts:writePOST / PUT / DELETE /v1/contacts
events:writePOST / PUT / DELETE /v1/events, POST /v1/event/mark
bookings:writePOST /v1/book_now
imports:writePOST /v1/import -- bulk-creates contacts/events; high impact.
lists:writePOST / PUT / DELETE /v1/lists
messaging:sendPOST /v1/start_conversation, POST /v1/conversation_reply -- sends SMS / email; billed and reaches end users.

Always available

GET /v1/userinfo requires only a valid token; it is not scope-gated.

Requesting multiple scopes

OAuth 2.0 scopes are space-delimited in the scope query parameter; URL-
encode the space as + or %20:

https://www.apptoto.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_REDIRECT_URI&
  response_type=code&
  scope=contacts:read+events:read+events:write

Calling an endpoint with a token that lacks the required scope returns:

HTTP/1.1 403 Forbidden
{"errors":"insufficient_scope","required_scope":"events:write"}

Revoking an Access Token

Programmatic revoke:

curl -X POST https://api.apptoto.com/oauth/revoke \
  -d token=YOUR_ACCESS_TOKEN \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET

Users can also revoke an application's access from the Apptoto portal
under Settings > Integrations > OAuth Applications > Connected Applications.

Linked Users

If the authorizing user has another user account set as their "API target"
in Apptoto, OAuth requests are forwarded to that target user automatically -
the same way HTTP Basic Authentication works. This means an integration
authorized by an agent account will act on the parent account's data when
the agent is configured that way.