How to get a permanent Access Token?

RESOLVED : Feel free to refer to my notion page (link) for all the steps I had to take.

Original Post

I’m not a programmer, but I wanted to set up keyboard shortcuts to control my Samsung TV (power on/off, switch HDMI, etc), and Perplexity helped me write the scripts that I can use with keyboard shortcut software (BetterTouchTool on Mac). I got the scripts to work as intended, but I learned that a personal token only lasts for 24 hours. Now I want to make it a more permanent solution.

I spent the last 2 hours trying to obtain an OAuth2 token, and I’m not getting anywhere.

  1. Installed the SmartThings CLI on my MacBook via Terminal
  2. Got a temporary webhoook.site redirection URL
  3. Created an OAuth-In app via Terminal (smartthings apps:create then verified with smartthings apps:oauth 1)
 Display Name     TV
 App Id           42431bd0-bfa2-...
 App Name         atv-f0c8a261-...
 Description      TV
 Single Instance  true
 Classifications  CONNECTED_SERVICE
 App Type         API_ONLY

 Scope          r:devices:$,r:devices:*,r:hubs:*,r:installedapps,r:locations:*,r:rules:*,r:scenes:*,w:devices:$,w:devices:*,w:installedapps,w:locations:*,w:rules:*,x:devices:$,x:devices:*,x:locations:*,x:scenes:*
 Redirect Uris  https://webhook.site/9ac56e98-....
  1. Initiate the Oauth process from web browser
https://api.smartthings.com/oauth/authorize?client_id=8cfa6928...(OAuth ClientID)&response_type=code&scope=r:devices:*,x:devices:*&redirect_uri=https://webhook.site/9ac56e98-...(WebhookURI)&state=xyz
  1. Authorize [My Home] from the UI

  2. From my browser, I see This URL has no default content configured error message, and from webhoook.site, I see under Query strings, error "server_error"

I have tried a few other troubleshooting solutions offered by Perplexity, but I’m stuck in a loop. Any recommendation?

1 Like

Following this.

Here is a description I wrote showing in basic terms how you can get access tokens and then how you can refresh them.

It looks like you are certainly on the right path but it may be worth taking webhook.site out of the loop as you are really just interested in the URL.

I had the same idea - a “quick” SmartThings OAuth setup for a Samsung TV (and some lights). Agony. I finally got it working, and here are my findings, in addition to the excellent instructions from orangebucket). Some of these notes are for ‘later in your journey’ - when you want to keep using access tokens …

  1. Here’s the URL that worked for me (client_id obfuscated, https://httpbin.org/get was my nominated redirect)…
    https://api.smartthings.com/oauth/authorize?client_id=6e98456b-8532-4d0e-b187-b8df44eff0c1&response_type=code&redirect_uri=https://httpbin.org/get&scope=r:devices:*%20w:devices:*%20x:devices:*%20r:locations:*%20w:locations:*%20r:scenes:*%20x:scenes:*%20r:installedapps:*%20w:installedapps:*
  2. I didn’t have &state=xyz on my URL.
  3. If Edge is your default browser some steps may not work (it failed for me) - it messes up the login process on the CLI, and (consequently) the redirect when that’s supposed happen. I changed to Chrome and it logged in and redirected. Before that I was getting a lot of those “server error” messages.
  4. Each and every access token is only valid for 24 hours so you have to refresh it every day if you want continuous access (86399 seconds = 23:59:59). I have an iOS Shortcut automation that runs every day to do this.
  5. Each refresh token is single-use. So when you use it to refresh, you need to keep the new refresh token (from the response) and discard the one you just used. It has to be an unbroken chain of refresh token usage.
  6. The access token expires when you refresh (or after 24 hours, whichever is the sooner). So you need to keep the new access token (from the response) and discard the one you have been using.
  7. If you refresh using an automated POST call (as I do in my daily shortcut, or you might in Python), you need the {client_id}:{secret} in a base64 encoded state. I use Data Jar in iOS Shortcuts to store:
    • base64 encoded {client_id}:{secret} (doesn’t change)
    • {client_id} (doesn’t change)
    • Current refresh token (replaced with new value each day)
    • Current access token (replaced with new value each day)
  8. If you refresh using curl, you don’t need base64 encoding, and the command will be:
    curl -X POST “https://api.smartthings.com/oauth/token” ^
    -u “{client_id}:{secret}” ^
    -H “Content-Type: application/x-www-form-urlencoded” ^
    -d “grant_type=refresh_token&client_id={client_id}&refresh_token={current refresh token}”

Hope this helps.
PS this is my first post, so apologies for any conventions or etiquette I have transgressed.

3 Likes

It is optional. For those not familiar with it, the state query parameter will be appended to the query string when the OAuth server (SmartThings in this case) redirects to your redirect_uri.

It doesn’t really gain you anything in the basically manual flow being described in this thread. However in an app where the flows are automated the state can be helpful in validating that the callback is genuine, and also help tie it to a specific request.

1 Like

Thanks @Digger68 @orangebucket

I finally got the 6 digit token with your help!

https://api.smartthings.com/oauth/authorize?client_id=[clientid]&response_type=code&redirect_uri=https://httpbin.org/get&scope=r:devices:*%20w:devices:*%20x:devices:*%20r:locations:*%20w:locations:*%20r:scenes:*%20x:scenes:*%20r:installedapps:*%20w:installedapps:*

worked flawlessly with Docker Desktop installed on my Mac, and an installation of Httpbin docker image, along with Port forwarding to my laptop. Not sure if this entire setup was necessary, but this setup gave me the 6 digit token I needed from my Mac Safari Browser.

With the token received, I did

curl -u "[client id]:[client secret]" https://api.smartthings.com/oauth/token -d "grant_type=authorization_code&client_id=[client id]&code=[6digit code]&redirect_uri=https://httpbin.org/get"

and was able to get the access token!

Now I need an easy way to refresh this token every 24 hours. I thought I would get a 30 day valid token, but I guess it’s not the case.

Is there any YouTube materials that I can refer to for doing Step 7?
I am stuck with not receiving any results.


Perplexity gave me this step-by-step guide to automate SmartThings token renewal using Google Cloud, store tokens in Google Drive, and use them with BetterTouchTool. but I find it overwhelming, and am looking for a simpler solution.


1. Prepare Python Script with Google Drive Upload

Modified Script (smartthings_token_refresh.py)

import requests
import json
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

# SmartThings OAuth2 credentials
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
REFRESH_TOKEN = "your_refresh_token"

# Google Drive folder ID (get from Drive URL)
GDRIVE_FOLDER_ID = "your_folder_id"

def refresh_smartthings_token():
    url = "https://api.smartthings.com/oauth/token"
    data = {
        "grant_type": "refresh_token",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "refresh_token": REFRESH_TOKEN
    }
    response = requests.post(url, data=data)
    return response.json()

def upload_to_drive(token_data):
    gauth = GoogleAuth()
    gauth.LocalWebserverAuth()  # Follow browser prompts to authenticate once
    drive = GoogleDrive(gauth)
    
    # Create/update token file
    file = drive.CreateFile({"title": "smartthings_token.json", "parents": [{"id": GDRIVE_FOLDER_ID}]})
    file.SetContentString(json.dumps(token_data))
    file.Upload()

if __name__ == "__main__":
    tokens = refresh_smartthings_token()
    upload_to_drive(tokens)

2. Set Up Google Cloud Project

  1. Create a Project

  2. Enable APIs

    • Google Drive API
    • Cloud Functions API
    • Cloud Scheduler API
  3. Create Service Account

    • IAM & Admin → Service Accounts → Create Service Account
    • Name: smartthings-token-refresh
    • Grant roles: Cloud Functions Admin, Service Account User
    • Generate JSON key and save it as credentials.json.

3. Configure Google Drive

  1. Create a Folder
    • In Google Drive, create a folder (e.g., “SmartThingsTokens”).
    • Share this folder with your service account email (from credentials.json).
    • Get the folder ID from the URL (e.g., https://drive.google.com/drive/u/0/folders/1aBcD... → ID is 1aBcD...).

4. Deploy to Google Cloud Functions

  1. Install Dependencies

    pip install requests pydrive google-api-python-client oauth2client
    
  2. Deploy Function

    gcloud functions deploy smartthings-token-refresh \
      --runtime python310 \
      --trigger-http \
      --entry-point refresh_smartthings_token \
      --service-account smartthings-token-refresh@YOUR_PROJECT.iam.gserviceaccount.com \
      --set-env-vars "CLIENT_ID=your_client_id,CLIENT_SECRET=your_client_secret,REFRESH_TOKEN=your_refresh_token,GDRIVE_FOLDER_ID=your_folder_id"
    

5. Schedule Daily Execution

gcloud scheduler jobs create http smartthings-token-refresh-job \
  --schedule "0 3 * * *" \  # Runs daily at 3 AM UTC
  --uri "https://YOUR_REGION-YOUR_PROJECT.cloudfunctions.net/smartthings-token-refresh" \
  --http-method GET

6. Sync Google Drive to Mac

  1. Install Google Drive for Desktop
    • Download from Google Drive.
    • Choose Mirror files for the “SmartThingsTokens” folder.
    • Local path: ~/Google Drive/My Drive/SmartThingsTokens/smartthings_token.json.

7. Configure BetterTouchTool

  1. Create a Shell Script Action

    #!/bin/zsh
    ACCESS_TOKEN=$(jq -r '.access_token' ~/Google\ Drive/My\ Drive/SmartThingsTokens/smartthings_token.json)
    
    # Example: Turn on a SmartThings switch
    curl -X POST "https://api.smartthings.com/v1/devices/YOUR_DEVICE_ID/commands" \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"commands": [{"component": "main","capability": "switch","command": "on"}]}'
    
  2. Assign to a Keyboard Shortcut

    • In BetterTouchTool, bind the script to a key combination (e.g., ⌃⌘T).

Cost Summary

  • Google Cloud Functions: Free (≤2M invocations/month).
  • Cloud Scheduler: Free (≤3 jobs/month).
  • Google Drive: Free (≤15GB storage).

Done! Your SmartThings token will refresh daily, sync to Google Drive, and be available locally for BetterTouchTool automations.

The real charm of AI is that it gives you the answer before you even figure out what you’re actually asking. You’re still just as confused, just with a solution in hand.

1 Like

@fpenguin not sure if you’ve got what you need yet, but in case it’s useful to have some notes for an iOS Shortcuts approach:

I have a shortcut that refreshes my access and refresh tokens. It’s based on the suite of useful SmartThings shortcuts created by @rambo350z [referenced here].

My “Get contents of URL” does look different from yours. I’ll include some screenshots that might show how.

This is the “body” …

Given the need for a persistent store for the access token and refresh token, I use Data Jar to store these. I also store the full ‘response’ each time I run the refresh, and the values I’ll need to construct the URL call:

The URL is https://api.smartthings.com/oauth/token.

Here’s my “Get contents of URL”:


… where ‘Renamed_item’ is the renamed text object ‘grant_type=refresh_token&client_id={Client_ID}&refresh_token={Refresh_Token}’ shown in a screenshot above. I don’t know if all that renaming is necessary… I have it working so I’m unwilling to fiddle with it. :slight_smile:

The “Authorization” header uses the base64 encoding of the {client_id}:{secret}. I used https://www.base64encode.org/ to create this.

Example of Success:

{"token_type":"bearer","scope":"r:locations:* x:devices:* r:scenes:* w:locations:* w:devices:* r:devices:* x:scenes:*","refresh_token":"65bd948c-a94b-407c-a993-05055541a187","access_tier":0,"installed_app_id":"91cc982a-8f6e-4941-a963-327d8637b176","access_token":"4f972753-368c-4b1f-860d-cf076101700d","expires_in":86399} 

Example of error:

{"error":"invalid_grant","error_description":"Invalid refresh token requested by clientId: 6e98456a-8532-4d0e-b187-b8df44eff0c1 with grantType: refresh_token, e.cause: no cause"}

Edit: I forgot to include the screenshot of what I do next: I store the response as a dictionary, extract the values for access_token and refresh_token, and finally store these in Data Jar so that I can use these for authorisation and refresh a day later:


Hope this is helpful.

1 Like

Made it work! Thank you so much! @Digger68

One trick was to change Line Break in Encode Setting from [Every 76 characters] (default) to [None].

Glad I could help. I didn’t know that there was a built-in base64 encoder – I’ll use that in future. So, thanks to you @fpenguin.

Incidentally, I have a Wordpress site that shows some summary information gleaned from ST such as the temperature at an elderly relative’s house, with conditional colours (blue, white, amber, red), so we know he’s okay. That obviously needs the access-code, so I’ve added a ‘POST’ API to my Wordpress website which updates the stored access-code; and a ‘GET’ to read the value.

Let me know if that’s of interest and I’ll post some details.

1 Like

Now it works flawlessly.

Feel free to take a look into the steps I had to take.

2 Likes

Did you get it to work? I did set up a docker for httpbin but I wasn’t entirely sure if this step was a necessity.

  1. Install Docker Desktop
  2. Add a container - https://hub.docker.com/r/kennethreitz/httpbin
  3. Run the container

Try the process and let me know if this has helped.
Wasn’t sure if you got it to work without this docker.

Yes thank you David!

I tried to send in the url without ”&scope”.
Then I got further in the web UI and was asked to select the scope in different optional check boxes. After that I received the 6 digit code :+1:

Now everything works great. I receive new tokens when running your iOS shortcut,
”SmartThings Token refresh” with values stored in ”Data Jar”

Thank you for creating and sharing this!
Mats

Glad to hear that my instructions were helpful.

Just to be sure (so that I can update my Notion doc), you were successful without having to install Docker, correct?

Yes. I followed your instruction but I took out the scope parameter in the url under point 5.

&scope=r:devices:,x:devices:

Skickat från Outlook för iOS