Header menu logo Threads

Insights Api

The insights API shares most of the fields between a thread and a user as a whole, so the way our types are modeled, you should be able to pull data without major issues sharing the same content.

#r "nuget: Threads.Lib"

open System
open Threads.Lib
open Threads.Lib.Insights

let client =
  Environment.GetEnvironmentVariable("THREADS_ACCESS_TOKEN") |> Threads.Create


type DataRange =
  | Week
  | Month
  | Year

let prepareInsightsParams range = [
  let minDate =
    // The Threads Insights API only supports data from after June 2, 2024
    DateTimeOffset(DateOnly(2024, 6, 2), TimeOnly(0, 0), TimeSpan.Zero)

  match range with
  | Week -> Since(DateTimeOffset.Now.AddDays(-7.))
  | Month -> Since(DateTimeOffset.Now.AddDays(-30.))
  | Year ->
    let targetDate = DateTimeOffset.Now.AddDays(-365.)

    let targetDate = if targetDate < minDate then minDate else targetDate

    Since(targetDate)

  Until(DateTimeOffset.Now)
]

task {

  let insightParams = prepareInsightsParams Year

  let! response =
    client.Insights.FetchUserInsights(
      "me",
      [ Views; Likes; Replies; Reposts; Quotes; FollowerCount ],
      insightParams
    )

  printfn "%A" response
}
|> Async.AwaitTask
|> Async.RunSynchronously

Running the code above will give you an output similar to this:

{ data =[
    [Name Views; Period Day; Title "Views";
     Description "Number of views of your profile.";
     Id "0123456789/insights/views/day";
     Values [{ value = 3u; endTime = ValueSome 6/1/2024 7:00:00

    [Name Likes; Period Day; Title "Likes";
     Description "Numberof Likes in your posts";
     Id "0123456789/insights/likes/day"; TotalValue 292u];

    [Name Replies; Period Day; Title "Replies";
     Description "Number of replies in your posts";
     Id "0123456789/insights/replies/day"; TotalValue 148u];

    [Name Reposts; Period Day; Title "reposts";
     Description "Number of reposts in your posts.";
     Id "0123456789/insights/reposts/day"; TotalValue 40u];

    [Name Quotes; Period Day; Title "citas";
     Description "Number of quotes of your posts.";
     Id "0123456789/insights/quotes/day"; TotalValue 50u];

    [Name FollowerCount; Period Day; Title "followers_count";
     Description "Number of your followers on threads.";
     Id "0123456789/insights/followers_count/day"; TotalValue 301u]
 ]
}

The only "row" that distinguishes itself from the others is the "Views" one where rather than delivering a "TotalValue" it brings a Values list, this can be plotted to have a nice view of how much views you were getting between periods of time If you add Plotly.NET you can easily generate an HTML embedable chart

#r "nuget: Threads.Lib, 1.0.0-beta-002"
#r "nuget: Plotly.NET,  5.0.0"

open System
// Added!
open Plotly.NET
open Threads.Lib
open Threads.Lib.Insights

let client =
  Environment.GetEnvironmentVariable("THREADS_ACCESS_TOKEN") |> Threads.Create

type DataRange =
  | Week
  | Month
  | Year

let prepareInsightsParams range = [
  let minDate =
    // The Threads Insights API only supports data from after June 2, 2024
    DateTimeOffset(DateOnly(2024, 6, 2), TimeOnly(0, 0), TimeSpan.Zero)

  match range with
  | Week -> Since(DateTimeOffset.Now.AddDays(-7.))
  | Month -> Since(DateTimeOffset.Now.AddDays(-30.))
  | Year ->
    let targetDate = DateTimeOffset.Now.AddDays(-365.)

    let targetDate = if targetDate < minDate then minDate else targetDate

    Since(targetDate)

  Until(DateTimeOffset.Now)
]

// Added!
let plotViews(views: MetricValue list) =
  let plotPoints =
    views
    |> List.map(fun v ->
      v.endTime.Value.DateTime.ToShortDateString(), int v.value)

  Chart.Line(plotPoints, Name = "Views")
  |> Chart.withTitle("Views over time")
  |> Chart.withSize(800., 600.)
  |> Chart.show

task {

  let insightParams = prepareInsightsParams Year

  let! response =
    client.Insights.FetchUserInsights(
      "me",
      [ Views; Likes; Replies; Reposts; Quotes; FollowerCount ],
      insightParams
    )

  let viewsOnly =
    let pickViews =
      List.tryPick(fun m ->
        match m with
        | Values v -> Some v
        | _ -> None)
    // Pick the first MetricValue list (which is usually the views one)
    // you can update the match expression above to be more rigorous about it
    List.tryPick (pickViews) response.data |> Option.defaultValue []

  plotViews viewsOnly
}
|> Async.AwaitTask
|> Async.RunSynchronously

If all goes well a new browser tab will be open with your interactive chart ready to go, in this case I just exported the image for you to see.

Views Plot

namespace System
val client: obj
type Environment = static member Exit: exitCode: int -> unit static member ExpandEnvironmentVariables: name: string -> string static member FailFast: message: string -> unit + 1 overload static member GetCommandLineArgs: unit -> string array static member GetEnvironmentVariable: variable: string -> string + 1 overload static member GetEnvironmentVariables: unit -> IDictionary + 1 overload static member GetFolderPath: folder: SpecialFolder -> string + 1 overload static member GetLogicalDrives: unit -> string array static member SetEnvironmentVariable: variable: string * value: string -> unit + 1 overload static member CommandLine: string ...
<summary>Provides information about, and means to manipulate, the current environment and platform. This class cannot be inherited.</summary>
Environment.GetEnvironmentVariable(variable: string) : string
Environment.GetEnvironmentVariable(variable: string, target: EnvironmentVariableTarget) : string
type DataRange = | Week | Month | Year
val prepareInsightsParams: range: DataRange -> 'a list
val range: DataRange
val minDate: DateTimeOffset
Multiple items
[<Struct>] type DateTimeOffset = new: dateTime: DateTime -> unit + 8 overloads member Add: timeSpan: TimeSpan -> DateTimeOffset member AddDays: days: float -> DateTimeOffset member AddHours: hours: float -> DateTimeOffset member AddMicroseconds: microseconds: float -> DateTimeOffset member AddMilliseconds: milliseconds: float -> DateTimeOffset member AddMinutes: minutes: float -> DateTimeOffset member AddMonths: months: int -> DateTimeOffset member AddSeconds: seconds: float -> DateTimeOffset member AddTicks: ticks: int64 -> DateTimeOffset ...
<summary>Represents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).</summary>

--------------------
DateTimeOffset ()
DateTimeOffset(dateTime: DateTime) : DateTimeOffset
DateTimeOffset(dateTime: DateTime, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(ticks: int64, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(date: DateOnly, time: TimeOnly, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, microsecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, microsecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
Multiple items
[<Struct>] type DateOnly = new: year: int * month: int * day: int -> unit + 1 overload member AddDays: value: int -> DateOnly member AddMonths: value: int -> DateOnly member AddYears: value: int -> DateOnly member CompareTo: value: DateOnly -> int + 1 overload member Deconstruct: year: byref<int> * month: byref<int> * day: byref<int> -> unit member Equals: value: DateOnly -> bool + 1 overload member GetHashCode: unit -> int member ToDateTime: time: TimeOnly -> DateTime + 1 overload member ToLongDateString: unit -> string ...
<summary>Represents dates with values ranging from January 1, 0001 Anno Domini (Common Era) through December 31, 9999 A.D. (C.E.) in the Gregorian calendar.</summary>

--------------------
DateOnly ()
DateOnly(year: int, month: int, day: int) : DateOnly
DateOnly(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateOnly
Multiple items
[<Struct>] type TimeOnly = new: hour: int * minute: int -> unit + 4 overloads member Add: value: TimeSpan -> TimeOnly + 1 overload member AddHours: value: float -> TimeOnly + 1 overload member AddMinutes: value: float -> TimeOnly + 1 overload member CompareTo: value: obj -> int + 1 overload member Deconstruct: hour: byref<int> * minute: byref<int> -> unit + 3 overloads member Equals: value: obj -> bool + 1 overload member GetHashCode: unit -> int member IsBetween: start: TimeOnly * ``end`` : TimeOnly -> bool member ToLongTimeString: unit -> string ...
<summary>Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999.</summary>

--------------------
TimeOnly ()
TimeOnly(ticks: int64) : TimeOnly
TimeOnly(hour: int, minute: int) : TimeOnly
TimeOnly(hour: int, minute: int, second: int) : TimeOnly
TimeOnly(hour: int, minute: int, second: int, millisecond: int) : TimeOnly
TimeOnly(hour: int, minute: int, second: int, millisecond: int, microsecond: int) : TimeOnly
Multiple items
[<Struct>] type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>

--------------------
TimeSpan ()
TimeSpan(ticks: int64) : TimeSpan
TimeSpan(hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : TimeSpan
field TimeSpan.Zero: TimeSpan
union case DataRange.Week: DataRange
property DateTimeOffset.Now: DateTimeOffset with get
<summary>Gets a <see cref="T:System.DateTimeOffset" /> object that is set to the current date and time on the current computer, with the offset set to the local time's offset from Coordinated Universal Time (UTC).</summary>
<returns>A <see cref="T:System.DateTimeOffset" /> object whose date and time is the current local time and whose offset is the local time zone's offset from Coordinated Universal Time (UTC).</returns>
DateTimeOffset.AddDays(days: float) : DateTimeOffset
union case DataRange.Month: DataRange
union case DataRange.Year: DataRange
val targetDate: DateTimeOffset
val task: TaskBuilder
val insightParams: obj list
val response: obj
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
Multiple items
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...

--------------------
type Async<'T>
static member Async.AwaitTask: task: Threading.Tasks.Task -> Async<unit>
static member Async.AwaitTask: task: Threading.Tasks.Task<'T> -> Async<'T>
static member Async.RunSynchronously: computation: Async<'T> * ?timeout: int * ?cancellationToken: Threading.CancellationToken -> 'T
union case ValueOption.ValueSome: 'T -> ValueOption<'T>
val plotViews: views: 'a list -> 'b
val views: 'a list
type 'T list = List<'T>
val plotPoints: (obj * int) list
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val v: 'a
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val viewsOnly: obj list
val pickViews: (obj list -> obj list option)
val tryPick: chooser: ('T -> 'U option) -> list: 'T list -> 'U option
val m: obj
val v: obj list
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
module Option from Microsoft.FSharp.Core
val defaultValue: value: 'T -> option: 'T option -> 'T

Type something to start searching.