Skip to content

LinkedIn account-scoped endpoints + DSC/existing-post creatives - #21

Merged
th0rz05 merged 1 commit into
mainfrom
pr2-linkedin-endpoints
Jun 2, 2026
Merged

LinkedIn account-scoped endpoints + DSC/existing-post creatives#21
th0rz05 merged 1 commit into
mainfrom
pr2-linkedin-endpoints

Conversation

@th0rz05

@th0rz05 th0rz05 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix LinkedIn write endpoints to use account-scoped paths.
  • Wire the Direct Sponsored Content (DSC) Posts API so creatives back a real post; ads can either reference a hand-published post via existing_post_urn (no posting permission needed) or mint a DSC dark post.
  • Add Campaign Group runSchedule, offsiteDeliveryEnabled + politicalIntent, locale, and B2B targeting passthrough.
  • Add focused tests for the client, mapping, and publish orchestration.

Second of 3 small PRs. Builds on #20 (dotenv loader).

Test plan

  • PYTHONPATH=src pytest -q (34 passed)
  • ruff check on changed files (only pre-existing main warnings remain)

Fixes the LinkedIn write endpoints to use account-scoped paths and wires
up the Direct Sponsored Content (DSC) Posts API so creatives can back a
real post. Ads may either reference a hand-published post via
`existing_post_urn` (no posting permission needed) or let the server mint
a DSC dark post. Adds Campaign Group runSchedule, offsiteDeliveryEnabled +
politicalIntent, locale, and B2B targeting passthrough, with focused tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 2, 2026 14:28
@th0rz05
th0rz05 merged commit 08524d8 into main Jun 2, 2026
1 check passed
@th0rz05
th0rz05 deleted the pr2-linkedin-endpoints branch June 2, 2026 14:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the LinkedIn integration to align with newer LinkedIn Marketing API requirements by switching write endpoints to account-scoped paths and introducing Direct Sponsored Content (DSC) post backing for creatives (either reusing an existing post via existing_post_urn or minting a dark post). It also adds Campaign Group runSchedule, plus campaign creation defaults for offsiteDeliveryEnabled and politicalIntent, with focused tests to pin these behaviors.

Changes:

  • Move LinkedIn write calls to account-scoped endpoints (/adAccounts/{id}/...) and add a read helper (list_campaigns).
  • Add DSC post creation + creative post references, including existing_post_urn passthrough in the domain model and agent prompts.
  • Add mapping + orchestration for Campaign Group runSchedule, plus client defaults/validation for offsiteDeliveryEnabled and politicalIntent, and new integration tests.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/yieldagent/integrations/linkedin/client.py Switch writes to account-scoped paths; add DSC create_post, creative intendedStatus, and campaign defaults/validation.
src/yieldagent/integrations/linkedin/server.py Publish orchestration now creates/reuses backing posts and sets Campaign Group runSchedule; loads dotenv in main().
src/yieldagent/integrations/linkedin/mapping.py Add campaign_run_schedule and new post/creative mapping helpers (post_article_content, post_commentary, creative_content_reference).
src/yieldagent/integrations/linkedin/config.py Add optional organization_urn and env parsing for org URN/ID; bump default API version.
src/yieldagent/domain/brief.py Introduce CreativeAsset.existing_post_urn field for reusing published posts.
src/yieldagent/agents/linkedin_setup/cli.py Load dotenv before CLI execution.
src/yieldagent/agents/campaign_setup/prompts.py Instruct planner to preserve/copy LinkedIn post URNs into existing_post_urn.
tests/integrations/test_linkedin_client.py New tests pin correct endpoint paths, payload constraints, and required headers.
tests/integrations/test_linkedin_mapping.py New tests for runSchedule and post/creative mapping helpers.
tests/integrations/test_linkedin_publish.py New orchestration tests for “reuse existing post” vs “mint dark post” branching.
docs/linkedin-integration.md Update documented default LinkedIn API version.
briefs/example_linkedin_brief.md Update example budget/KPI values and document how to specify an existing post URN.
tests/integrations/__init__.py Package marker for integration tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +43
# Accept a bare org id or a full URN.
org = os.environ.get("LINKEDIN_ORGANIZATION_URN") or os.environ.get(
"LINKEDIN_ORGANIZATION_ID"
)
if org and not org.startswith("urn:li:organization:"):
org = f"urn:li:organization:{org.strip()}"
`adContext.dscAdAccount`. `dscAdType` must NOT be sent — it is read-only and
a 422 ("ReadOnly field present in a create request") results otherwise.
"""
payload: dict[str, Any] = {
article=post_article_content(ad.creative),
dsc_ad_account_urn=config.account_urn,
)
post_urn = post.get("id")
Comment on lines +6 to 8
Optional env: LINKEDIN_API_VERSION (default 202605),
LINKEDIN_ALLOWED_AD_ACCOUNTS (comma-separated allowlist),
YIELDAGENT_ALLOW_LIVE (set to 1 to bypass the allowlist)
Comment on lines 44 to 48
| Variable | Default | Meaning |
|---|---|---|
| `LINKEDIN_API_VERSION` | `202405` | Versioned API header (`LinkedIn-Version`). |
| `LINKEDIN_API_VERSION` | `202605` | Versioned API header (`LinkedIn-Version`). |
| `LINKEDIN_ALLOWED_AD_ACCOUNTS` | unset | Comma-separated allowlist of ad account ids. The configured account must be in the list, or `YIELDAGENT_ALLOW_LIVE=1` must be set. |
| `YIELDAGENT_ALLOW_LIVE` | unset | Set to `1` to bypass the allowlist. Disables the only safety net LinkedIn offers through this integration — use sparingly. |
Comment on lines +3 to +8
These pin the endpoint paths the client uses for writes. LinkedIn deprecated
the global write endpoints (`/adCampaignGroups`, `/adCampaigns`, `/creatives`)
in favour of account-scoped ones (`/adAccounts/{id}/...`). The old endpoints
return 400 with an explicit migration message — see
`docs/claude_docs/linkedin_campaign_manager_api_debug_prompt.md`.

Comment on lines +49 to +55
@pytest.fixture
def recorded_client():
recorder = _Recorder()
transport = httpx.MockTransport(recorder.handler)
http = httpx.AsyncClient(transport=transport, timeout=5.0)
client = LinkedInClient(_make_config(), http=http)
yield client, recorder
th0rz05 added a commit that referenced this pull request Jun 9, 2026
)

Fixes the LinkedIn write endpoints to use account-scoped paths and wires
up the Direct Sponsored Content (DSC) Posts API so creatives can back a
real post. Ads may either reference a hand-published post via
`existing_post_urn` (no posting permission needed) or let the server mint
a DSC dark post. Adds Campaign Group runSchedule, offsiteDeliveryEnabled +
politicalIntent, locale, and B2B targeting passthrough, with focused tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants