Forum Discussion
Using Web Activity's output as a source of Copy Data Activity.
- 1 year ago
Hi younghoon_kim đ
Thanks for the extra details in your follow-up thread. I can see the API returns:
"next": "/details?q=..."
âŚand when you use AbsoluteUrl = $.next, the next call is sent to:https://serviceportal.telenorconnexion.com/details?... (â base path dropped)
instead of:https://serviceportal.telenorconnexion.com/iot/api/subscriptions/details?... (â expected)
That explains the failure you saw. When the API gives a root-relative path (starts with â/â), the Copy activity treats it as domain-root and ignores the base path segment (/iot/api/subscriptions).
Below are three clean ways to get you unblockedâstart with Option A if you can make the API return a full link.A) Easiest (if API can return a full link)
Ask the API (or toggle an option, if it has one) to return an absolute next link:
"next": "https://serviceportal.telenorconnexion.com/iot/api/subscriptions/details?q=..."
Then set Source ⸠Advanced ⸠Pagination rules:Key: AbsoluteUrl
Value: $.next
Thatâs the officially supported pattern: AbsoluteUrl can point to the next absolute or relative URL in the response body; JSONPath is used to read it. Also add an EndCondition or MaxRequestNumber to avoid endless loops if the API echoes back the last URL.
B) When the API only returns a root-relative /details?...Copy activity canât âprependâ /iot/api/subscriptions/ to a root-relative value in the pagination rules (the value must be a header reference or a JSONPath resultâno string concat). Thatâs why the dynamic @concat(...) you tried errors (ânot an ancestorâ â pagination rules canât reference other activities).
Two reliable workarounds:B1) Dataflow Gen2 (Power Query) paging
Power Query lets you stitch the base path with the relative ânextâ easily.
Dataflow Gen2 ⸠Blank query ⸠Advanced Editor, paste a template like:
let
Base = "https://serviceportal.telenorconnexion.com/iot/api/subscriptions",
First = "/details?{your params}",
GetPage = (rel as text) =>
let
url = Base & rel,
json = Json.Document(Web.Contents(url)),
rows = json[data],
next = try json[next] otherwise null
in [Rows = rows, Next = next],Source = Table.GenerateByPage((prev) =>
let rel = if prev = null then First else prev[Next]
in if rel = null then null else GetPage(rel)
),Result = Table.ExpandListColumn(Source, "Rows")
in
Result
Output to your Lakehouse table.
Docs: Handling paging with Table.GenerateByPage().B2) Notebook (Python) â full control
If you prefer code, resolve the relative next with urljoin:
import requests, pandas as pd
from urllib.parse import urljoinbase = "https://serviceportal.telenorconnexion.com/iot/api/subscriptions/"
url = urljoin(base, "details?{your params}")
rows = []while url:
r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=60)
r.raise_for_status()
js = r.json()
rows.extend(js.get("data", []))
next_rel = js.get("next")
url = urljoin(base, next_rel) if next_rel else Nonedf = pd.DataFrame(rows)
# write to Lakehouse table/files as you prefer
This precisely fixes the root-relative next issue by always combining it with the correct base path. (General REST + pagination guidance for Copy/REST is here.)
C) Small safety knobs (whichever route you take)EndCondition / MaxRequestNumber in pagination to prevent endless loops.
Request interval (ms) (e.g., 300â500) if the API rate-limits.
If this solved it, please mark as Solution and give Kudos so others can find it faster đ
Hi younghoon_kim đ
Thanks for the extra details in your follow-up thread. I can see the API returns:
"next": "/details?q=..."
âŚand when you use AbsoluteUrl = $.next, the next call is sent to:
https://serviceportal.telenorconnexion.com/details?... (â base path dropped)
instead of:
https://serviceportal.telenorconnexion.com/iot/api/subscriptions/details?... (â expected)
That explains the failure you saw. When the API gives a root-relative path (starts with â/â), the Copy activity treats it as domain-root and ignores the base path segment (/iot/api/subscriptions).
Below are three clean ways to get you unblockedâstart with Option A if you can make the API return a full link.
A) Easiest (if API can return a full link)
Ask the API (or toggle an option, if it has one) to return an absolute next link:
"next": "https://serviceportal.telenorconnexion.com/iot/api/subscriptions/details?q=..."
Then set Source ⸠Advanced ⸠Pagination rules:
Key: AbsoluteUrl
Value: $.next
Thatâs the officially supported pattern: AbsoluteUrl can point to the next absolute or relative URL in the response body; JSONPath is used to read it. Also add an EndCondition or MaxRequestNumber to avoid endless loops if the API echoes back the last URL.
B) When the API only returns a root-relative /details?...
Copy activity canât âprependâ /iot/api/subscriptions/ to a root-relative value in the pagination rules (the value must be a header reference or a JSONPath resultâno string concat). Thatâs why the dynamic @concat(...) you tried errors (ânot an ancestorâ â pagination rules canât reference other activities).
Two reliable workarounds:
B1) Dataflow Gen2 (Power Query) paging
Power Query lets you stitch the base path with the relative ânextâ easily.
Dataflow Gen2 ⸠Blank query ⸠Advanced Editor, paste a template like:
let
Base = "https://serviceportal.telenorconnexion.com/iot/api/subscriptions",
First = "/details?{your params}",
GetPage = (rel as text) =>
let
url = Base & rel,
json = Json.Document(Web.Contents(url)),
rows = json[data],
next = try json[next] otherwise null
in [Rows = rows, Next = next],
Source = Table.GenerateByPage((prev) =>
let rel = if prev = null then First else prev[Next]
in if rel = null then null else GetPage(rel)
),
Result = Table.ExpandListColumn(Source, "Rows")
in
Result
Output to your Lakehouse table.
Docs: Handling paging with Table.GenerateByPage().
B2) Notebook (Python) â full control
If you prefer code, resolve the relative next with urljoin:
import requests, pandas as pd
from urllib.parse import urljoin
base = "https://serviceportal.telenorconnexion.com/iot/api/subscriptions/"
url = urljoin(base, "details?{your params}")
rows = []
while url:
r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=60)
r.raise_for_status()
js = r.json()
rows.extend(js.get("data", []))
next_rel = js.get("next")
url = urljoin(base, next_rel) if next_rel else None
df = pd.DataFrame(rows)
# write to Lakehouse table/files as you prefer
This precisely fixes the root-relative next issue by always combining it with the correct base path. (General REST + pagination guidance for Copy/REST is here.)
C) Small safety knobs (whichever route you take)
EndCondition / MaxRequestNumber in pagination to prevent endless loops.
Request interval (ms) (e.g., 300â500) if the API rate-limits.
If this solved it, please mark as Solution and give Kudos so others can find it faster đ
Hi Aala_Ali , Thanks for your descriptive answer. I knew Notebook always an option but was just curious if what I want is feasible in Copy Activity. Also great to know that Dataflow Gen2 can do this too. I'm not used to using it, but will reference you answer when I decide to use it. Thanks. FYI, now I'm focusing ELT jobs on Data Pipeline, so I configured using Copy Activity, Variables, and Until Activity, and it works
- Aala_Ali1 year agoMost Valuable Professional
Hi younghoon_kim
Awesome, Iâm really happy to hear you got it working with Copy activity + Variables + Until.