diff --git a/cmd/campaign-service/kodata/gen/http/openapi.json b/cmd/campaign-service/kodata/gen/http/openapi.json index a91e4ac4..44812a60 100644 --- a/cmd/campaign-service/kodata/gen/http/openapi.json +++ b/cmd/campaign-service/kodata/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/projects/{project_id}/connection-google-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"description":"A connection already exists for this provider on the project","example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"title":"ConnServiceUnavailableError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"title":"ConnectionTestResult","type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Labore aut eum porro."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":true}},"example":{"message":"Necessitatibus consequatur natus tempora ut.","ok":true},"required":["ok"]},"GoogleAdsConnection":{"title":"GoogleAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Aut voluptatem inventore ab."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quis et maxime ipsam voluptatem repudiandae laudantium."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis ipsam."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Iusto quis magni et velit."},"project_id":{"type":"string","description":"Owning project","example":"Ut ipsum id."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8470973010316083422,"format":"int64"}},"example":{"account_id":"Aut id molestiae sit.","has_credentials":true,"id":"Magni voluptas odio assumenda totam.","label":"Nam cumque.","login_customer_id":"Qui esse.","project_id":"Est praesentium dolor officiis deleniti.","status":"active","version":7862773009082385007},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"title":"GoogleAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"title":"GoogleAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ducimus sequi."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Autem ab voluptas culpa."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Quia tenetur ducimus."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Aut et."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Alias non sequi consequatur dolorum.","client_secret":"Voluptatem voluptas omnis est tempora ipsum aut.","developer_token":"Harum et minus voluptas.","refresh_token":"Facere harum enim consequatur id voluptatem."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"title":"HubspotConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Sed maiores qui sed exercitationem quam."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Asperiores facere et exercitationem soluta possimus."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"In expedita."},"label":{"type":"string","description":"Optional friendly name","example":"Suscipit sed minus soluta quasi."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Quia ducimus."},"project_id":{"type":"string","description":"Owning project","example":"Magnam cumque quasi magni ducimus."},"sender_email":{"type":"string","description":"Default sender address","example":"Et veritatis voluptas nobis."},"sender_name":{"type":"string","description":"Default sender name","example":"Doloremque architecto ea et."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4054743500330389510,"format":"int64"}},"example":{"account_id":"Est iste.","brand_kit":"Quae aut culpa impedit sit.","has_credentials":true,"id":"Aliquid et in.","label":"Distinctio aut.","portal_id":"Earum modi esse.","project_id":"Consequuntur omnis a dignissimos voluptate.","sender_email":"Possimus placeat enim ab.","sender_name":"Esse et qui et voluptatibus tempore.","status":"active","version":3449749392517333935},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"title":"HubspotConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Velit labore non."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Dolorem sapiente."},"label":{"type":"string","description":"Optional friendly name","example":"Nulla adipisci aperiam dolorem molestias."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Odit est esse."},"sender_email":{"type":"string","description":"Default sender address","example":"Ea cum et magni adipisci magnam."},"sender_name":{"type":"string","description":"Default sender name","example":"Atque labore qui odio et corporis."}},"example":{"account_id":"Sint autem.","brand_kit":"Debitis soluta sed quod dicta aperiam eveniet.","label":"Aut natus.","portal_id":"Deleniti itaque voluptas magnam.","sender_email":"Voluptatem modi velit vitae optio ut.","sender_name":"Consequuntur sit libero autem ut."},"required":["account_id"]},"HubspotCredentials":{"title":"HubspotCredentials","type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Dolor id."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Accusantium sequi modi corrupti esse mollitia."},"required":["private_app_token"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"},"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}},"required":["config"]},"LinkedinAdsConnection":{"title":"LinkedinAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vel blanditiis sunt ut facere."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsa nihil ad hic repellendus nobis voluptatem."},"label":{"type":"string","description":"Optional friendly name","example":"Rerum cupiditate."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Vel molestiae consequatur."},"project_id":{"type":"string","description":"Owning project","example":"Vero et voluptas accusamus sunt."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4079703323268843140,"format":"int64"}},"example":{"account_id":"Et corporis officia sint.","has_credentials":false,"id":"Quidem est non tenetur dolore.","label":"Cupiditate voluptatem.","org_id":"Quia nulla autem ratione.","project_id":"Dolores commodi aperiam voluptatibus.","status":"error","version":874315130556833044},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"title":"LinkedinAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"In mollitia a magni."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Consequatur ea deserunt tempora aut assumenda.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"title":"LinkedinAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Occaecati eum tenetur."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Inventore aliquid voluptatibus iste mollitia."},"required":["access_token"]},"MetaAdsConnection":{"title":"MetaAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Asperiores ipsum quam."},"app_id":{"type":"string","description":"Meta app ID","example":"Cum rem architecto vel quibusdam vel autem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Libero atque est non praesentium."},"label":{"type":"string","description":"Optional friendly name","example":"Id tenetur exercitationem aperiam incidunt."},"page_id":{"type":"string","description":"Facebook page ID","example":"Qui suscipit rerum quo eligendi tempora."},"project_id":{"type":"string","description":"Owning project","example":"Saepe sit et sed qui dolorem reiciendis."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5968285761891001183,"format":"int64"}},"example":{"account_id":"Quibusdam aliquam consequuntur.","app_id":"Deleniti earum magni quibusdam.","has_credentials":false,"id":"Id facere maxime.","label":"Maxime ut possimus expedita.","page_id":"Facilis facilis aut suscipit aut odio iste.","project_id":"Hic delectus.","status":"active","version":6674224742141168677},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"title":"MetaAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Harum provident quia distinctio ea."},"label":{"type":"string","description":"Optional friendly name","example":"Tenetur velit quaerat aut."},"page_id":{"type":"string","description":"Facebook page ID","example":"Aut et."}},"example":{"account_id":"act_193556282970417","app_id":"Blanditiis atque rerum et officiis sapiente et.","label":"Rem harum provident provident quia laboriosam magnam.","page_id":"Hic et sed."},"required":["account_id"]},"MetaAdsCredentials":{"title":"MetaAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Itaque dolores in hic rem."},"app_secret":{"type":"string","description":"Meta app secret","example":"Eligendi ea vel consequatur nihil."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Vero commodi est qui asperiores velit enim.","app_secret":"Expedita ut minus ut est recusandae aspernatur."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"title":"MicrosoftAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ipsum voluptatem et quam beatae laborum omnis."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Rem suscipit similique ipsam aut quis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Repudiandae voluptatem vitae iste maxime modi laborum."},"label":{"type":"string","description":"Optional friendly name","example":"Magnam fugit."},"project_id":{"type":"string","description":"Owning project","example":"Dignissimos necessitatibus consequuntur."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7512765750150729777,"format":"int64"}},"example":{"account_id":"Nobis nam.","customer_id":"Reiciendis pariatur quisquam.","has_credentials":true,"id":"Laudantium dolore.","label":"Non quidem doloribus debitis itaque itaque dolores.","project_id":"Possimus nihil qui enim ratione voluptatem eos.","status":"error","version":7616558197057713508},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"title":"MicrosoftAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Aspernatur facere."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Voluptatem dicta rerum sed."},"label":{"type":"string","description":"Optional friendly name","example":"Aspernatur ea voluptatem eum voluptas eveniet assumenda."}},"example":{"account_id":"Quia vero quis consequuntur id.","customer_id":"Quo illum cum quod.","label":"Quia possimus dolore vel qui."},"required":["account_id"]},"MicrosoftAdsCredentials":{"title":"MicrosoftAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ut fugiat magnam quia."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Ut tempora consequuntur cumque repudiandae omnis ut."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Est aut labore sint natus."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Distinctio beatae."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Ducimus debitis nesciunt quas quibusdam occaecati cum.","client_secret":"Iste voluptatem et voluptatem ut ut.","developer_token":"Quia sed omnis quaerat aut.","refresh_token":"Nostrum laborum minima."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"description":"Resource not found","example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PreconditionFailedError":{"title":"PreconditionFailedError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"description":"ETag mismatch","example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"title":"PreconditionRequiredError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"description":"If-Match header required","example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"title":"RedditAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ipsam temporibus sint et necessitatibus dolorem ut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fugit voluptas molestias architecto dicta quidem illo."},"label":{"type":"string","description":"Optional friendly name","example":"Quia aut sed fugiat adipisci eos rerum."},"project_id":{"type":"string","description":"Owning project","example":"Doloremque et qui perferendis."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1442766184420227830,"format":"int64"}},"example":{"account_id":"Voluptates neque expedita.","has_credentials":false,"id":"Qui sed fugiat.","label":"Sit iusto.","project_id":"Veniam debitis aut similique ullam temporibus qui.","status":"active","version":3889986261433875806},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"title":"RedditAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Quisquam ut eius."}},"example":{"account_id":"t2_gv9wtbfa","label":"Ipsum nihil cumque repellendus sint soluta."},"required":["account_id"]},"RedditAdsCredentials":{"title":"RedditAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Sed quibusdam reiciendis qui vel."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Amet et eaque."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Consequatur iste sed illo adipisci sunt."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Soluta consectetur porro quo quia incidunt.","client_secret":"Aperiam et ipsam vitae.","refresh_token":"Velit est quis aliquid aliquam."},"required":["client_id","client_secret","refresh_token"]},"TwitterAdsConnection":{"title":"TwitterAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vitae quae debitis sunt aperiam ex voluptatibus."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"In fuga ut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Voluptate nisi natus sint."},"label":{"type":"string","description":"Optional friendly name","example":"Ut dolor velit."},"project_id":{"type":"string","description":"Owning project","example":"Beatae aliquam quia officiis hic ea."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6056629402053782931,"format":"int64"}},"example":{"account_id":"Non possimus quos voluptatem doloremque consequatur deleniti.","funding_instrument_id":"Fuga illo dolorem sit ex commodi sapiente.","has_credentials":false,"id":"Iusto quidem laudantium.","label":"Quaerat ut neque expedita nostrum.","project_id":"Ratione consequuntur aut et corrupti.","status":"error","version":5145245548849296683},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"title":"TwitterAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Excepturi dolorem autem ipsa autem provident."},"label":{"type":"string","description":"Optional friendly name","example":"Illum tenetur numquam."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Quis et odio tenetur optio.","label":"Iure quo culpa."},"required":["account_id"]},"TwitterAdsCredentials":{"title":"TwitterAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Veritatis excepturi veniam maxime incidunt asperiores voluptatem."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Nostrum dolorum iure eaque animi."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Ut sint."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Iste eveniet laudantium rerum."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Error necessitatibus et autem doloribus est.","access_token_secret":"Maxime labore illo maxime provident.","consumer_key":"Error laudantium.","consumer_secret":"Similique odio adipisci dolore iste."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"JWT issued by Heimdall; audience is this service.","name":"Authorization","in":"header"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/projects/{project_id}/briefs":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-brief lfx-v2-campaign-service-briefs","description":"Create a brief.","operationId":"lfx-v2-campaign-service-briefs#create-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-BriefRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsCreateBriefRequestBody","required":["brief"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-brief lfx-v2-campaign-service-briefs","description":"Get a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-brief lfx-v2-campaign-service-briefs","description":"Replace a brief (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-BriefRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsUpdateBriefRequestBody","required":["brief"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"delete-brief lfx-v2-campaign-service-briefs","description":"Archive a brief (soft delete).","operationId":"lfx-v2-campaign-service-briefs#delete-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/approve":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"approve-brief lfx-v2-campaign-service-briefs","description":"Approve a brief for campaign creation.","operationId":"lfx-v2-campaign-service-briefs#approve-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-campaigns lfx-v2-campaign-service-briefs","description":"Create campaigns across the selected platforms (async -\u003e job).","operationId":"lfx-v2-campaign-service-briefs#create-campaigns","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-CampaignsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsCreateCampaignsRequestBody","required":["input"]}}],"responses":{"202":{"description":"Accepted response.","schema":{"$ref":"#/definitions/JobCreateResponse","required":["job_id","status","platforms"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-campaign lfx-v2-campaign-service-briefs","description":"Get one campaign under a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Campaign","required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-campaign lfx-v2-campaign-service-briefs","description":"Replace a campaign (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-CampaignRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsUpdateCampaignRequestBody","required":["campaign"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Campaign","required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/jobs/{job_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-job lfx-v2-campaign-service-briefs","description":"Poll campaign-creation job status.","operationId":"lfx-v2-campaign-service-briefs#get-job","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"job_id","in":"path","description":"Job UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/JobPollResponse","required":["job_id","status"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"Brief":{"title":"Brief","type":"object","properties":{"copy":{"description":"Ad copy","example":"Ut mollitia nihil ducimus."},"event_details":{"description":"Extracted event/course details","example":"Sunt quae et nesciunt iure quis."},"event_slug":{"type":"string","description":"Event/course slug","example":"Possimus dolores dignissimos beatae velit laudantium."},"id":{"type":"string","description":"Brief UUID","example":"Officiis beatae sed aut."},"keywords":{"description":"Keyword list","example":"Non quo necessitatibus."},"platforms":{"type":"array","items":{"type":"string","example":"Id eum aut et itaque."},"description":"Suggested default platforms","example":["Et deleniti dolores libero facilis qui.","Natus facere qui omnis et enim.","Officia dolorem odio ut beatae quia qui.","Minima illo dolores natus a sint."]},"program_type":{"type":"string","description":"Funnel context","example":"Est fugiat aut dicta libero iure culpa."},"project_id":{"type":"string","description":"Owning project","example":"Nihil est quia qui fugiat est."},"status":{"type":"string","description":"Lifecycle status","example":"approved","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Molestiae est accusantium laudantium nostrum."},"url":{"type":"string","description":"Event/course page URL","example":"Eum et aut quia beatae harum similique."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":757787044039705195,"format":"int64"}},"example":{"copy":"Earum minus autem omnis qui debitis eum.","event_details":"Cupiditate mollitia hic vel ut.","event_slug":"Fugit dolor et natus sit cupiditate non.","id":"Eos exercitationem.","keywords":"Sed veniam dolores quidem necessitatibus voluptatem.","platforms":["Esse officia explicabo dolor deserunt aspernatur.","Aspernatur illo accusamus."],"program_type":"Pariatur magni atque amet omnis doloremque.","project_id":"Nihil neque quod alias ipsum.","status":"approved","targeting":"Sit reiciendis et vel beatae molestiae.","url":"Modi possimus consequatur.","version":5414041088513005988},"required":["id","project_id","program_type","event_slug","status","version"]},"BriefInput":{"title":"BriefInput","type":"object","properties":{"copy":{"description":"Ad copy","example":"Ipsam ut sed natus et unde."},"event_details":{"description":"Extracted event/course details","example":"Qui impedit temporibus eveniet eos omnis cumque."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Eos sit aut."},"keywords":{"description":"Keyword list","example":"Explicabo repudiandae aut reiciendis recusandae molestias."},"platforms":{"type":"array","items":{"type":"string","example":"Esse consectetur tenetur."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Aut ut sit nisi architecto.","Rerum et recusandae possimus quis.","Praesentium voluptate."]},"program_type":{"type":"string","description":"Funnel context","example":"membership","enum":["events","education","membership"]},"targeting":{"description":"Targeting recommendation","example":"Vel inventore aliquid aut non eligendi accusamus."},"url":{"type":"string","description":"Event/course page URL","example":"Est sed corrupti sit eos labore."}},"example":{"copy":"Quasi in aut nam rerum.","event_details":"Error perferendis quae qui inventore dolorem.","event_slug":"Impedit accusamus incidunt minus veritatis enim.","keywords":"Distinctio magnam.","platforms":["Id corrupti sed quo perferendis nemo.","Quisquam nisi repellendus quos magnam aut.","Id voluptatem nihil quaerat asperiores.","Repellat vel veritatis hic rerum accusamus."],"program_type":"membership","targeting":"Architecto deserunt vero ducimus distinctio.","url":"Hic est quia pariatur odit ullam."},"required":["program_type","event_slug"]},"Campaign":{"title":"Campaign","type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Fugiat aliquid aspernatur voluptatem in cupiditate."},"campaign_name":{"type":"string","description":"Campaign name","example":"Nostrum atque illum nesciunt adipisci odit corrupti."},"id":{"type":"string","description":"Campaign UUID","example":"Architecto quae error."},"platform":{"type":"string","description":"Channel","example":"Et dolorem quod."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Iusto fugit non asperiores quidem natus."},"project_id":{"type":"string","description":"Owning project","example":"Placeat et minus et."},"status":{"type":"string","description":"Campaign status","example":"Consequatur hic voluptatem."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4612952422408203887,"format":"int64"}},"example":{"brief_id":"Consectetur occaecati earum unde ut sint.","campaign_name":"Hic quibusdam eveniet nostrum doloribus voluptas mollitia.","id":"Quidem dolor ipsum.","platform":"Eum expedita non voluptas sit.","platform_campaign_id":"Ad ab quasi tempora dolor quis.","project_id":"Sed occaecati id ea sunt dolorem necessitatibus.","status":"Totam cum veniam.","version":8632050897094076248},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"CampaignCreateInput":{"title":"CampaignCreateInput","type":"object","properties":{"config":{"description":"Per-platform campaign configuration","example":"Ut nobis."},"platforms":{"type":"array","items":{"type":"string","example":"google-ads","enum":["google-ads","linkedin-ads","meta-ads","reddit-ads","twitter-ads","microsoft-ads","hubspot"]},"description":"Platforms to create campaigns on (binding selection)","example":["meta-ads","google-ads"],"minItems":1}},"example":{"config":"Rem aliquam animi harum.","platforms":["hubspot"]},"required":["platforms"]},"CampaignUpdateInput":{"title":"CampaignUpdateInput","type":"object","properties":{"campaign_name":{"type":"string","description":"Campaign name","example":"Quos amet."},"config":{"description":"Campaign configuration snapshot","example":"Quidem omnis totam eos non molestias."},"status":{"type":"string","description":"Campaign status","example":"Quia doloremque commodi rerum."}},"example":{"campaign_name":"Quia iusto maxime.","config":"Error sit nihil iure officiis nemo.","status":"Illum accusamus debitis explicabo praesentium sint."},"required":["campaign_name","status"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"description":"Conflict","example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"title":"ConnServiceUnavailableError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"title":"ConnectionTestResult","type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Est omnis iusto fugiat ad et ipsa."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":false}},"example":{"message":"Veritatis mollitia.","ok":true},"required":["ok"]},"GoogleAdsConnection":{"title":"GoogleAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vero ut et neque soluta distinctio voluptas."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Rem asperiores eligendi vero."},"label":{"type":"string","description":"Optional friendly name","example":"Facere quas qui aut rerum."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Fugit eum."},"project_id":{"type":"string","description":"Owning project","example":"Voluptatem fugiat et."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5717670313645740058,"format":"int64"}},"example":{"account_id":"Occaecati quaerat eum quia a excepturi.","has_credentials":true,"id":"Necessitatibus voluptatem incidunt et totam esse rerum.","label":"Voluptatem incidunt cum.","login_customer_id":"Eos culpa quaerat culpa inventore aut molestiae.","project_id":"Dolorem delectus.","status":"deleted","version":4898302691270586418},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"title":"GoogleAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"title":"GoogleAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Id vel iusto optio repellendus."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Ad dolorem et."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Rerum eveniet nam sunt."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Hic quos inventore."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Nisi est magni odio aut.","client_secret":"Vel omnis iure.","developer_token":"Voluptatem doloremque ullam consequatur ut.","refresh_token":"Inventore impedit unde voluptatem quia ab recusandae."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"title":"HubspotConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Incidunt voluptas non necessitatibus."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Provident porro."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Qui nemo nihil dolorum consequatur."},"label":{"type":"string","description":"Optional friendly name","example":"Est ea sed sit."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Deserunt nostrum consectetur et sed praesentium libero."},"project_id":{"type":"string","description":"Owning project","example":"Tempora qui eum hic vel aspernatur at."},"sender_email":{"type":"string","description":"Default sender address","example":"Qui minima in nulla sunt autem eos."},"sender_name":{"type":"string","description":"Default sender name","example":"Provident dicta tenetur voluptatem voluptate dolor."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5710646536103854438,"format":"int64"}},"example":{"account_id":"Est quis impedit.","brand_kit":"Dolor quam dolor consectetur eos iste.","has_credentials":true,"id":"Odio sunt esse mollitia incidunt.","label":"Veritatis itaque.","portal_id":"Consequatur eligendi eum necessitatibus alias.","project_id":"Dolores laborum sed.","sender_email":"Ut aperiam voluptate voluptates.","sender_name":"Sapiente molestiae dolorum repellat expedita ut.","status":"inactive","version":6098388766312520476},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"title":"HubspotConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Veniam iusto consequatur quia sed officia eligendi."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Veniam delectus repellendus omnis."},"label":{"type":"string","description":"Optional friendly name","example":"Praesentium et."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Qui impedit."},"sender_email":{"type":"string","description":"Default sender address","example":"Ea alias."},"sender_name":{"type":"string","description":"Default sender name","example":"Reprehenderit dignissimos aut velit quis at sint."}},"example":{"account_id":"Quo necessitatibus necessitatibus.","brand_kit":"Repudiandae tenetur aperiam saepe vel.","label":"Amet cum.","portal_id":"Sint dicta.","sender_email":"Omnis voluptatem rerum praesentium.","sender_name":"Blanditiis non totam temporibus."},"required":["account_id"]},"HubspotCredentials":{"title":"HubspotCredentials","type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Animi sit esse ratione aliquid."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Laborum est quasi dignissimos."},"required":["private_app_token"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"JobCreateResponse":{"title":"JobCreateResponse","type":"object","properties":{"job_id":{"type":"string","description":"Poll GET /projects/{projectId}/jobs/{jobId}","example":"Quia magni aut nisi est."},"platforms":{"type":"array","items":{"type":"string","example":"Est sed."},"description":"Platforms this job will create on","example":["Dolorem necessitatibus illum.","Ex ipsa nihil esse voluptate in consectetur.","Culpa quaerat.","Sed voluptatum et impedit quod rem."]},"status":{"type":"string","description":"Initial status (always 'queued' on create)","example":"queued","enum":["queued"]}},"example":{"job_id":"Omnis corrupti facere eum aliquid est repellat.","platforms":["Id incidunt mollitia non sed.","Minima est distinctio accusamus.","Perferendis culpa voluptatem nostrum eaque deleniti."],"status":"queued"},"required":["job_id","status","platforms"]},"JobPollResponse":{"title":"JobPollResponse","type":"object","properties":{"error":{"type":"string","description":"Terminal error, if any","example":"Ut voluptatem."},"job_id":{"type":"string","description":"Job UUID","example":"Molestias ducimus id."},"result":{"type":"array","items":{"$ref":"#/definitions/PlatformResult"},"description":"Per-platform results, written once when the job reaches a terminal state","example":[{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."}]},"status":{"type":"string","description":"Job status","example":"queued","enum":["queued","running","succeeded","partial","failed"]}},"example":{"error":"Aperiam facere voluptas quis nihil.","job_id":"Vel iste aut cum voluptatem.","result":[{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."}],"status":"partial"},"required":["job_id","status"]},"LfxV2CampaignServiceBriefsCreateBriefRequestBody":{"title":"LfxV2CampaignServiceBriefsCreateBriefRequestBody","type":"object","properties":{"brief":{"$ref":"#/definitions/BriefInput"}},"example":{"brief":{"copy":"Blanditiis quidem qui iusto sed.","event_details":"Dolor molestias ipsa dolorum est.","event_slug":"Accusantium ad.","keywords":"Vel fuga recusandae.","platforms":["Iste autem veniam et enim unde.","Facere quam.","Rem quia consequuntur ut.","Consequatur quos atque voluptates quo sit."],"program_type":"membership","targeting":"Nulla ut laboriosam.","url":"Modi asperiores ut libero vel."}},"required":["brief"]},"LfxV2CampaignServiceBriefsCreateCampaignsRequestBody":{"title":"LfxV2CampaignServiceBriefsCreateCampaignsRequestBody","type":"object","properties":{"input":{"$ref":"#/definitions/CampaignCreateInput"}},"example":{"input":{"config":"Facere et exercitationem.","platforms":["microsoft-ads","twitter-ads","google-ads"]}},"required":["input"]},"LfxV2CampaignServiceBriefsUpdateBriefRequestBody":{"title":"LfxV2CampaignServiceBriefsUpdateBriefRequestBody","type":"object","properties":{"brief":{"$ref":"#/definitions/BriefInput"}},"example":{"brief":{"copy":"Blanditiis quidem qui iusto sed.","event_details":"Dolor molestias ipsa dolorum est.","event_slug":"Accusantium ad.","keywords":"Vel fuga recusandae.","platforms":["Iste autem veniam et enim unde.","Facere quam.","Rem quia consequuntur ut.","Consequatur quos atque voluptates quo sit."],"program_type":"membership","targeting":"Nulla ut laboriosam.","url":"Modi asperiores ut libero vel."}},"required":["brief"]},"LfxV2CampaignServiceBriefsUpdateCampaignRequestBody":{"title":"LfxV2CampaignServiceBriefsUpdateCampaignRequestBody","type":"object","properties":{"campaign":{"$ref":"#/definitions/CampaignUpdateInput"}},"example":{"campaign":{"campaign_name":"Ad voluptatem.","config":"Et expedita itaque quos.","status":"Tempore impedit."}},"required":["campaign"]},"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"},"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."},"credentials":{"private_app_token":"Molestiae suscipit non iusto."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"},"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."},"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."},"credentials":{"client_id":"Nisi blanditiis eveniet quasi ea.","client_secret":"Ut corporis dolorum consequatur eius perferendis nobis.","developer_token":"Et et itaque sunt.","refresh_token":"Vel voluptate necessitatibus."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."},"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."},"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Molestiae suscipit non iusto."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"credentials":{"client_id":"Nisi blanditiis eveniet quasi ea.","client_secret":"Ut corporis dolorum consequatur eius perferendis nobis.","developer_token":"Et et itaque sunt.","refresh_token":"Vel voluptate necessitatibus."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."}},"required":["config"]},"LinkedinAdsConnection":{"title":"LinkedinAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Esse et labore earum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Velit ea eum perferendis qui qui."},"label":{"type":"string","description":"Optional friendly name","example":"Tempore rem quo fuga accusantium voluptas ratione."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Velit quod cumque perspiciatis."},"project_id":{"type":"string","description":"Owning project","example":"Repudiandae quae aut."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7189689836865162887,"format":"int64"}},"example":{"account_id":"Tenetur veniam repellat.","has_credentials":false,"id":"Iste possimus ipsam perspiciatis enim id expedita.","label":"Doloribus at vel necessitatibus vel.","org_id":"Cumque optio impedit.","project_id":"Quia voluptatem impedit.","status":"error","version":5803043317553025654},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"title":"LinkedinAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Aut repudiandae voluptatibus odit autem."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Dicta qui.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"title":"LinkedinAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Et earum id."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Voluptate dolorum id ut molestias id."},"required":["access_token"]},"MetaAdsConnection":{"title":"MetaAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Autem aliquam voluptatibus assumenda mollitia molestias eos."},"app_id":{"type":"string","description":"Meta app ID","example":"Fugit eum et sint possimus omnis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsa aut."},"label":{"type":"string","description":"Optional friendly name","example":"Impedit vitae molestiae."},"page_id":{"type":"string","description":"Facebook page ID","example":"Et consequatur vel quisquam."},"project_id":{"type":"string","description":"Owning project","example":"Non assumenda molestiae."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1948493999542129316,"format":"int64"}},"example":{"account_id":"Vel temporibus sunt nulla esse sed.","app_id":"Sed ea qui pariatur adipisci doloremque.","has_credentials":false,"id":"Neque hic voluptatem ad similique sunt.","label":"Ratione impedit recusandae dolor consequatur.","page_id":"Veritatis perferendis veritatis libero.","project_id":"Doloremque dicta.","status":"deleted","version":8079523500580416969},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"title":"MetaAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Recusandae est perferendis eos et cumque."},"label":{"type":"string","description":"Optional friendly name","example":"Esse dolores aut."},"page_id":{"type":"string","description":"Facebook page ID","example":"Exercitationem voluptate quia esse nihil."}},"example":{"account_id":"act_193556282970417","app_id":"Laborum omnis.","label":"Esse neque maiores quo aliquam.","page_id":"Rem eveniet in autem neque."},"required":["account_id"]},"MetaAdsCredentials":{"title":"MetaAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Voluptatem possimus veniam dignissimos consequatur."},"app_secret":{"type":"string","description":"Meta app secret","example":"Delectus enim quas id porro."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Est porro eos ipsa quia.","app_secret":"Quo repudiandae."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"title":"MicrosoftAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Modi esse voluptatibus."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Quo veritatis id accusamus hic."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Veniam molestias."},"label":{"type":"string","description":"Optional friendly name","example":"Tenetur cumque rerum."},"project_id":{"type":"string","description":"Owning project","example":"Accusamus quia velit commodi."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2983004504871549622,"format":"int64"}},"example":{"account_id":"Inventore quisquam praesentium nihil atque et nihil.","customer_id":"Ad ex.","has_credentials":true,"id":"Officia et optio sed omnis aut voluptatem.","label":"Maxime et ea et.","project_id":"Nisi eum omnis est.","status":"inactive","version":3453857184328446715},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"title":"MicrosoftAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Atque reprehenderit."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Eligendi incidunt accusamus excepturi."},"label":{"type":"string","description":"Optional friendly name","example":"Dolore delectus ut beatae eaque."}},"example":{"account_id":"Molestiae id aut debitis iure.","customer_id":"Dolore est dolorem quod enim pariatur.","label":"Qui est ut voluptas quod minima dolor."},"required":["account_id"]},"MicrosoftAdsCredentials":{"title":"MicrosoftAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Occaecati veritatis."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Aut architecto aliquam mollitia labore sapiente."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Quia mollitia."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Tenetur non accusamus qui ex."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Ex et amet sunt.","client_secret":"Ad quae nisi.","developer_token":"Cum et ab ipsum.","refresh_token":"Reiciendis enim."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"description":"Resource not found","example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PlatformResult":{"title":"PlatformResult","type":"object","properties":{"campaign_id":{"type":"string","description":"Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost.","example":"Ex et alias ea asperiores quas."},"error":{"type":"string","description":"Failure reason (present when not ok)","example":"Corporis dolorem laborum maiores est ea ducimus."},"ok":{"type":"boolean","description":"Whether the campaign was created (or reused) successfully","example":true},"platform":{"type":"string","description":"Platform this result is for","example":"Aut quo."}},"example":{"campaign_id":"Ullam ad iusto quos et est.","error":"Explicabo aliquid.","ok":false,"platform":"Dolorem dignissimos."},"required":["platform","ok"]},"PreconditionFailedError":{"title":"PreconditionFailedError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"description":"ETag mismatch","example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"title":"PreconditionRequiredError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"description":"If-Match header required","example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"title":"RedditAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Aut omnis itaque ut vero voluptatibus."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Non eos."},"label":{"type":"string","description":"Optional friendly name","example":"Enim voluptatem inventore quasi."},"project_id":{"type":"string","description":"Owning project","example":"Vel aut aliquam rerum et quidem."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6330819027913820088,"format":"int64"}},"example":{"account_id":"Quibusdam repellendus qui numquam.","has_credentials":true,"id":"Voluptatibus voluptatem vero dolorem qui.","label":"Vero vel.","project_id":"Sequi non et.","status":"inactive","version":4305260724068280609},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"title":"RedditAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"A voluptatem ipsa et voluptatem."}},"example":{"account_id":"t2_gv9wtbfa","label":"Est voluptatibus placeat sint pariatur."},"required":["account_id"]},"RedditAdsCredentials":{"title":"RedditAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Explicabo expedita et velit accusamus."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Deleniti placeat rerum."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Nulla eos."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Recusandae totam soluta.","client_secret":"Praesentium libero.","refresh_token":"Qui et assumenda quas."},"required":["client_id","client_secret","refresh_token"]},"TwitterAdsConnection":{"title":"TwitterAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Qui sed voluptatibus quam quia."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Sapiente laudantium molestiae omnis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quisquam animi."},"label":{"type":"string","description":"Optional friendly name","example":"Repudiandae facilis blanditiis libero nam."},"project_id":{"type":"string","description":"Owning project","example":"Delectus blanditiis magnam sed iure amet."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":9048631027735279786,"format":"int64"}},"example":{"account_id":"Officiis aliquid explicabo eaque ab.","funding_instrument_id":"Ea iusto eveniet velit sint odit sed.","has_credentials":true,"id":"Consequuntur perspiciatis modi.","label":"Vero et atque in impedit velit omnis.","project_id":"Quae sed.","status":"inactive","version":187687687190489160},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"title":"TwitterAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Esse voluptate optio quis quae quam."},"label":{"type":"string","description":"Optional friendly name","example":"Eos nemo cupiditate."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Pariatur nostrum.","label":"Odio quia omnis commodi."},"required":["account_id"]},"TwitterAdsCredentials":{"title":"TwitterAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Voluptas ut vero autem consequuntur optio omnis."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Pariatur omnis ut velit dolorem dolor."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Illo porro velit perferendis."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Ipsum aut voluptas molestias unde."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Mollitia nulla.","access_token_secret":"Minus voluptatem ab delectus porro.","consumer_key":"Omnis in qui.","consumer_secret":"Nihil ut."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"JWT issued by Heimdall; audience is this service.","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/cmd/campaign-service/kodata/gen/http/openapi.yaml b/cmd/campaign-service/kodata/gen/http/openapi.yaml index b20cb445..816318a4 100644 --- a/cmd/campaign-service/kodata/gen/http/openapi.yaml +++ b/cmd/campaign-service/kodata/gen/http/openapi.yaml @@ -13,6 +13,708 @@ produces: - application/xml - application/gob paths: + /projects/{project_id}/briefs: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-brief lfx-v2-campaign-service-briefs + description: Create a brief. + operationId: lfx-v2-campaign-service-briefs#create-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: Create-BriefRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsCreateBriefRequestBody' + required: + - brief + responses: + "201": + description: Created response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-brief lfx-v2-campaign-service-briefs + description: Get a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-brief lfx-v2-campaign-service-briefs + description: Replace a brief (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + - name: Update-BriefRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsUpdateBriefRequestBody' + required: + - brief + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + delete: + tags: + - lfx-v2-campaign-service-briefs + summary: delete-brief lfx-v2-campaign-service-briefs + description: Archive a brief (soft delete). + operationId: lfx-v2-campaign-service-briefs#delete-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "204": + description: No Content response. + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/approve: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: approve-brief lfx-v2-campaign-service-briefs + description: Approve a brief for campaign creation. + operationId: lfx-v2-campaign-service-briefs#approve-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-campaigns lfx-v2-campaign-service-briefs + description: Create campaigns across the selected platforms (async -> job). + operationId: lfx-v2-campaign-service-briefs#create-campaigns + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: Create-CampaignsRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsCreateCampaignsRequestBody' + required: + - input + responses: + "202": + description: Accepted response. + schema: + $ref: '#/definitions/JobCreateResponse' + required: + - job_id + - status + - platforms + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-campaign lfx-v2-campaign-service-briefs + description: Get one campaign under a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: campaign_id + in: path + description: Campaign UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Campaign' + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-campaign lfx-v2-campaign-service-briefs + description: Replace a campaign (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: campaign_id + in: path + description: Campaign UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + - name: Update-CampaignRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsUpdateCampaignRequestBody' + required: + - campaign + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Campaign' + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] /projects/{project_id}/connection-google-ads: get: tags: @@ -2659,6 +3361,77 @@ paths: - http security: - jwt_header_Authorization: [] + /projects/{project_id}/jobs/{job_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-job lfx-v2-campaign-service-briefs + description: Poll campaign-creation job status. + operationId: lfx-v2-campaign-service-briefs#get-job + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: job_id + in: path + description: Job UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/JobPollResponse' + required: + - job_id + - status + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] definitions: BadRequestError: title: BadRequestError @@ -2679,6 +3452,253 @@ definitions: required: - code - message + Brief: + title: Brief + type: object + properties: + copy: + description: Ad copy + example: Ut mollitia nihil ducimus. + event_details: + description: Extracted event/course details + example: Sunt quae et nesciunt iure quis. + event_slug: + type: string + description: Event/course slug + example: Possimus dolores dignissimos beatae velit laudantium. + id: + type: string + description: Brief UUID + example: Officiis beatae sed aut. + keywords: + description: Keyword list + example: Non quo necessitatibus. + platforms: + type: array + items: + type: string + example: Id eum aut et itaque. + description: Suggested default platforms + example: + - Et deleniti dolores libero facilis qui. + - Natus facere qui omnis et enim. + - Officia dolorem odio ut beatae quia qui. + - Minima illo dolores natus a sint. + program_type: + type: string + description: Funnel context + example: Est fugiat aut dicta libero iure culpa. + project_id: + type: string + description: Owning project + example: Nihil est quia qui fugiat est. + status: + type: string + description: Lifecycle status + example: approved + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Molestiae est accusantium laudantium nostrum. + url: + type: string + description: Event/course page URL + example: Eum et aut quia beatae harum similique. + version: + type: integer + description: Optimistic-concurrency version + example: 757787044039705195 + format: int64 + example: + copy: Earum minus autem omnis qui debitis eum. + event_details: Cupiditate mollitia hic vel ut. + event_slug: Fugit dolor et natus sit cupiditate non. + id: Eos exercitationem. + keywords: Sed veniam dolores quidem necessitatibus voluptatem. + platforms: + - Esse officia explicabo dolor deserunt aspernatur. + - Aspernatur illo accusamus. + program_type: Pariatur magni atque amet omnis doloremque. + project_id: Nihil neque quod alias ipsum. + status: approved + targeting: Sit reiciendis et vel beatae molestiae. + url: Modi possimus consequatur. + version: 5414041088513005988 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + BriefInput: + title: BriefInput + type: object + properties: + copy: + description: Ad copy + example: Ipsam ut sed natus et unde. + event_details: + description: Extracted event/course details + example: Qui impedit temporibus eveniet eos omnis cumque. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Eos sit aut. + keywords: + description: Keyword list + example: Explicabo repudiandae aut reiciendis recusandae molestias. + platforms: + type: array + items: + type: string + example: Esse consectetur tenetur. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Aut ut sit nisi architecto. + - Rerum et recusandae possimus quis. + - Praesentium voluptate. + program_type: + type: string + description: Funnel context + example: membership + enum: + - events + - education + - membership + targeting: + description: Targeting recommendation + example: Vel inventore aliquid aut non eligendi accusamus. + url: + type: string + description: Event/course page URL + example: Est sed corrupti sit eos labore. + example: + copy: Quasi in aut nam rerum. + event_details: Error perferendis quae qui inventore dolorem. + event_slug: Impedit accusamus incidunt minus veritatis enim. + keywords: Distinctio magnam. + platforms: + - Id corrupti sed quo perferendis nemo. + - Quisquam nisi repellendus quos magnam aut. + - Id voluptatem nihil quaerat asperiores. + - Repellat vel veritatis hic rerum accusamus. + program_type: membership + targeting: Architecto deserunt vero ducimus distinctio. + url: Hic est quia pariatur odit ullam. + required: + - program_type + - event_slug + Campaign: + title: Campaign + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Fugiat aliquid aspernatur voluptatem in cupiditate. + campaign_name: + type: string + description: Campaign name + example: Nostrum atque illum nesciunt adipisci odit corrupti. + id: + type: string + description: Campaign UUID + example: Architecto quae error. + platform: + type: string + description: Channel + example: Et dolorem quod. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Iusto fugit non asperiores quidem natus. + project_id: + type: string + description: Owning project + example: Placeat et minus et. + status: + type: string + description: Campaign status + example: Consequatur hic voluptatem. + version: + type: integer + description: Optimistic-concurrency version + example: 4612952422408203887 + format: int64 + example: + brief_id: Consectetur occaecati earum unde ut sint. + campaign_name: Hic quibusdam eveniet nostrum doloribus voluptas mollitia. + id: Quidem dolor ipsum. + platform: Eum expedita non voluptas sit. + platform_campaign_id: Ad ab quasi tempora dolor quis. + project_id: Sed occaecati id ea sunt dolorem necessitatibus. + status: Totam cum veniam. + version: 8632050897094076248 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + CampaignCreateInput: + title: CampaignCreateInput + type: object + properties: + config: + description: Per-platform campaign configuration + example: Ut nobis. + platforms: + type: array + items: + type: string + example: google-ads + enum: + - google-ads + - linkedin-ads + - meta-ads + - reddit-ads + - twitter-ads + - microsoft-ads + - hubspot + description: Platforms to create campaigns on (binding selection) + example: + - meta-ads + - google-ads + minItems: 1 + example: + config: Rem aliquam animi harum. + platforms: + - hubspot + required: + - platforms + CampaignUpdateInput: + title: CampaignUpdateInput + type: object + properties: + campaign_name: + type: string + description: Campaign name + example: Quos amet. + config: + description: Campaign configuration snapshot + example: Quidem omnis totam eos non molestias. + status: + type: string + description: Campaign status + example: Quia doloremque commodi rerum. + example: + campaign_name: Quia iusto maxime. + config: Error sit nihil iure officiis nemo. + status: Illum accusamus debitis explicabo praesentium sint. + required: + - campaign_name + - status ConflictError: title: ConflictError type: object @@ -2691,7 +3711,7 @@ definitions: type: string description: Error message example: A connection for this provider already exists on the project. - description: A connection already exists for this provider on the project + description: Conflict example: code: "409" message: A connection for this provider already exists on the project. @@ -2724,13 +3744,13 @@ definitions: message: type: string description: Human-readable detail - example: Labore aut eum porro. + example: Est omnis iusto fugiat ad et ipsa. ok: type: boolean description: Whether the credential authenticated against the provider - example: true + example: false example: - message: Necessitatibus consequatur natus tempora ut. + message: Veritatis mollitia. ok: true required: - ok @@ -2741,31 +3761,31 @@ definitions: account_id: type: string description: Provider account identifier - example: Aut voluptatem inventore ab. + example: Vero ut et neque soluta distinctio voluptas. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Quis et maxime ipsam voluptatem repudiandae laudantium. + example: Rem asperiores eligendi vero. label: type: string description: Optional friendly name - example: Nobis ipsam. + example: Facere quas qui aut rerum. login_customer_id: type: string description: Manager account used for API access - example: Iusto quis magni et velit. + example: Fugit eum. project_id: type: string description: Owning project - example: Ut ipsum id. + example: Voluptatem fugiat et. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -2774,17 +3794,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 8470973010316083422 + example: 5717670313645740058 format: int64 example: - account_id: Aut id molestiae sit. + account_id: Occaecati quaerat eum quia a excepturi. has_credentials: true - id: Magni voluptas odio assumenda totam. - label: Nam cumque. - login_customer_id: Qui esse. - project_id: Est praesentium dolor officiis deleniti. - status: active - version: 7862773009082385007 + id: Necessitatibus voluptatem incidunt et totam esse rerum. + label: Voluptatem incidunt cum. + login_customer_id: Eos culpa quaerat culpa inventore aut molestiae. + project_id: Dolorem delectus. + status: deleted + version: 4898302691270586418 required: - id - project_id @@ -2821,25 +3841,25 @@ definitions: client_id: type: string description: OAuth client id - example: Ducimus sequi. + example: Id vel iusto optio repellendus. client_secret: type: string description: OAuth client secret - example: Autem ab voluptas culpa. + example: Ad dolorem et. developer_token: type: string description: Google Ads developer token - example: Quia tenetur ducimus. + example: Rerum eveniet nam sunt. refresh_token: type: string description: OAuth refresh token - example: Aut et. + example: Hic quos inventore. description: Google Ads OAuth credential set. Write-only; never returned. example: - client_id: Alias non sequi consequatur dolorum. - client_secret: Voluptatem voluptas omnis est tempora ipsum aut. - developer_token: Harum et minus voluptas. - refresh_token: Facere harum enim consequatur id voluptatem. + client_id: Nisi est magni odio aut. + client_secret: Vel omnis iure. + developer_token: Voluptatem doloremque ullam consequatur ut. + refresh_token: Inventore impedit unde voluptatem quia ab recusandae. required: - refresh_token - client_id @@ -2852,11 +3872,11 @@ definitions: account_id: type: string description: Provider account identifier - example: Sed maiores qui sed exercitationem quam. + example: Incidunt voluptas non necessitatibus. brand_kit: type: string description: Per-project brand kit selector - example: Asperiores facere et exercitationem soluta possimus. + example: Provident porro. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -2864,27 +3884,27 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: In expedita. + example: Qui nemo nihil dolorum consequatur. label: type: string description: Optional friendly name - example: Suscipit sed minus soluta quasi. + example: Est ea sed sit. portal_id: type: string description: HubSpot portal/account ID - example: Quia ducimus. + example: Deserunt nostrum consectetur et sed praesentium libero. project_id: type: string description: Owning project - example: Magnam cumque quasi magni ducimus. + example: Tempora qui eum hic vel aspernatur at. sender_email: type: string description: Default sender address - example: Et veritatis voluptas nobis. + example: Qui minima in nulla sunt autem eos. sender_name: type: string description: Default sender name - example: Doloremque architecto ea et. + example: Provident dicta tenetur voluptatem voluptate dolor. status: type: string description: Connection status @@ -2897,20 +3917,20 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 4054743500330389510 + example: 5710646536103854438 format: int64 example: - account_id: Est iste. - brand_kit: Quae aut culpa impedit sit. + account_id: Est quis impedit. + brand_kit: Dolor quam dolor consectetur eos iste. has_credentials: true - id: Aliquid et in. - label: Distinctio aut. - portal_id: Earum modi esse. - project_id: Consequuntur omnis a dignissimos voluptate. - sender_email: Possimus placeat enim ab. - sender_name: Esse et qui et voluptatibus tempore. - status: active - version: 3449749392517333935 + id: Odio sunt esse mollitia incidunt. + label: Veritatis itaque. + portal_id: Consequatur eligendi eum necessitatibus alias. + project_id: Dolores laborum sed. + sender_email: Ut aperiam voluptate voluptates. + sender_name: Sapiente molestiae dolorum repellat expedita ut. + status: inactive + version: 6098388766312520476 required: - id - project_id @@ -2925,34 +3945,34 @@ definitions: account_id: type: string description: HubSpot list/audience ID - example: Velit labore non. + example: Veniam iusto consequatur quia sed officia eligendi. brand_kit: type: string description: Per-project brand kit selector - example: Dolorem sapiente. + example: Veniam delectus repellendus omnis. label: type: string description: Optional friendly name - example: Nulla adipisci aperiam dolorem molestias. + example: Praesentium et. portal_id: type: string description: HubSpot portal/account ID - example: Odit est esse. + example: Qui impedit. sender_email: type: string description: Default sender address - example: Ea cum et magni adipisci magnam. + example: Ea alias. sender_name: type: string description: Default sender name - example: Atque labore qui odio et corporis. + example: Reprehenderit dignissimos aut velit quis at sint. example: - account_id: Sint autem. - brand_kit: Debitis soluta sed quod dicta aperiam eveniet. - label: Aut natus. - portal_id: Deleniti itaque voluptas magnam. - sender_email: Voluptatem modi velit vitae optio ut. - sender_name: Consequuntur sit libero autem ut. + account_id: Quo necessitatibus necessitatibus. + brand_kit: Repudiandae tenetur aperiam saepe vel. + label: Amet cum. + portal_id: Sint dicta. + sender_email: Omnis voluptatem rerum praesentium. + sender_name: Blanditiis non totam temporibus. required: - account_id HubspotCredentials: @@ -2962,10 +3982,10 @@ definitions: private_app_token: type: string description: HubSpot private app token - example: Dolor id. + example: Animi sit esse ratione aliquid. description: HubSpot private app token. Write-only; never returned. example: - private_app_token: Accusantium sequi modi corrupti esse mollitia. + private_app_token: Laborum est quasi dignissimos. required: - private_app_token InternalServerError: @@ -2987,6 +4007,174 @@ definitions: required: - code - message + JobCreateResponse: + title: JobCreateResponse + type: object + properties: + job_id: + type: string + description: Poll GET /projects/{projectId}/jobs/{jobId} + example: Quia magni aut nisi est. + platforms: + type: array + items: + type: string + example: Est sed. + description: Platforms this job will create on + example: + - Dolorem necessitatibus illum. + - Ex ipsa nihil esse voluptate in consectetur. + - Culpa quaerat. + - Sed voluptatum et impedit quod rem. + status: + type: string + description: Initial status (always 'queued' on create) + example: queued + enum: + - queued + example: + job_id: Omnis corrupti facere eum aliquid est repellat. + platforms: + - Id incidunt mollitia non sed. + - Minima est distinctio accusamus. + - Perferendis culpa voluptatem nostrum eaque deleniti. + status: queued + required: + - job_id + - status + - platforms + JobPollResponse: + title: JobPollResponse + type: object + properties: + error: + type: string + description: Terminal error, if any + example: Ut voluptatem. + job_id: + type: string + description: Job UUID + example: Molestias ducimus id. + result: + type: array + items: + $ref: '#/definitions/PlatformResult' + description: Per-platform results, written once when the job reaches a terminal state + example: + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + status: + type: string + description: Job status + example: queued + enum: + - queued + - running + - succeeded + - partial + - failed + example: + error: Aperiam facere voluptas quis nihil. + job_id: Vel iste aut cum voluptatem. + result: + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + status: partial + required: + - job_id + - status + LfxV2CampaignServiceBriefsCreateBriefRequestBody: + title: LfxV2CampaignServiceBriefsCreateBriefRequestBody + type: object + properties: + brief: + $ref: '#/definitions/BriefInput' + example: + brief: + copy: Blanditiis quidem qui iusto sed. + event_details: Dolor molestias ipsa dolorum est. + event_slug: Accusantium ad. + keywords: Vel fuga recusandae. + platforms: + - Iste autem veniam et enim unde. + - Facere quam. + - Rem quia consequuntur ut. + - Consequatur quos atque voluptates quo sit. + program_type: membership + targeting: Nulla ut laboriosam. + url: Modi asperiores ut libero vel. + required: + - brief + LfxV2CampaignServiceBriefsCreateCampaignsRequestBody: + title: LfxV2CampaignServiceBriefsCreateCampaignsRequestBody + type: object + properties: + input: + $ref: '#/definitions/CampaignCreateInput' + example: + input: + config: Facere et exercitationem. + platforms: + - microsoft-ads + - twitter-ads + - google-ads + required: + - input + LfxV2CampaignServiceBriefsUpdateBriefRequestBody: + title: LfxV2CampaignServiceBriefsUpdateBriefRequestBody + type: object + properties: + brief: + $ref: '#/definitions/BriefInput' + example: + brief: + copy: Blanditiis quidem qui iusto sed. + event_details: Dolor molestias ipsa dolorum est. + event_slug: Accusantium ad. + keywords: Vel fuga recusandae. + platforms: + - Iste autem veniam et enim unde. + - Facere quam. + - Rem quia consequuntur ut. + - Consequatur quos atque voluptates quo sit. + program_type: membership + targeting: Nulla ut laboriosam. + url: Modi asperiores ut libero vel. + required: + - brief + LfxV2CampaignServiceBriefsUpdateCampaignRequestBody: + title: LfxV2CampaignServiceBriefsUpdateCampaignRequestBody + type: object + properties: + campaign: + $ref: '#/definitions/CampaignUpdateInput' + example: + campaign: + campaign_name: Ad voluptatem. + config: Et expedita itaque quos. + status: Tempore impedit. + required: + - campaign LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody: title: LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody type: object @@ -3001,10 +4189,10 @@ definitions: label: TLF Main login_customer_id: "9746983954" credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. required: - config - credentials @@ -3018,14 +4206,14 @@ definitions: $ref: '#/definitions/HubspotCredentials' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. required: - config - credentials @@ -3040,10 +4228,10 @@ definitions: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. required: - config - credentials @@ -3058,12 +4246,12 @@ definitions: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. required: - config - credentials @@ -3077,14 +4265,14 @@ definitions: $ref: '#/definitions/MicrosoftAdsCredentials' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Nisi blanditiis eveniet quasi ea. + client_secret: Ut corporis dolorum consequatur eius perferendis nobis. + developer_token: Et et itaque sunt. + refresh_token: Vel voluptate necessitatibus. required: - config - credentials @@ -3099,11 +4287,11 @@ definitions: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. required: - config - credentials @@ -3118,13 +4306,13 @@ definitions: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. required: - config - credentials @@ -3136,10 +4324,10 @@ definitions: $ref: '#/definitions/GoogleAdsCredentials' example: credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody: @@ -3150,7 +4338,7 @@ definitions: $ref: '#/definitions/HubspotCredentials' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody: @@ -3161,7 +4349,7 @@ definitions: $ref: '#/definitions/LinkedinAdsCredentials' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody: @@ -3172,8 +4360,8 @@ definitions: $ref: '#/definitions/MetaAdsCredentials' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody: @@ -3184,10 +4372,10 @@ definitions: $ref: '#/definitions/MicrosoftAdsCredentials' example: credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Nisi blanditiis eveniet quasi ea. + client_secret: Ut corporis dolorum consequatur eius perferendis nobis. + developer_token: Et et itaque sunt. + refresh_token: Vel voluptate necessitatibus. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody: @@ -3198,9 +4386,9 @@ definitions: $ref: '#/definitions/RedditAdsCredentials' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody: @@ -3211,10 +4399,10 @@ definitions: $ref: '#/definitions/TwitterAdsCredentials' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. required: - credentials LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody: @@ -3238,12 +4426,12 @@ definitions: $ref: '#/definitions/HubspotConnectionConfig' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. required: - config LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody: @@ -3255,7 +4443,7 @@ definitions: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" required: - config @@ -3268,9 +4456,9 @@ definitions: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. required: - config LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody: @@ -3281,9 +4469,9 @@ definitions: $ref: '#/definitions/MicrosoftAdsConnectionConfig' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. required: - config LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody: @@ -3295,7 +4483,7 @@ definitions: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. required: - config LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody: @@ -3307,8 +4495,8 @@ definitions: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. required: - config LinkedinAdsConnection: @@ -3318,27 +4506,27 @@ definitions: account_id: type: string description: Provider account identifier - example: Vel blanditiis sunt ut facere. + example: Esse et labore earum. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Ipsa nihil ad hic repellendus nobis voluptatem. + example: Velit ea eum perferendis qui qui. label: type: string description: Optional friendly name - example: Rerum cupiditate. + example: Tempore rem quo fuga accusantium voluptas ratione. org_id: type: string description: LinkedIn organization URN id - example: Vel molestiae consequatur. + example: Velit quod cumque perspiciatis. project_id: type: string description: Owning project - example: Vero et voluptas accusamus sunt. + example: Repudiandae quae aut. status: type: string description: Connection status @@ -3351,17 +4539,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 4079703323268843140 + example: 7189689836865162887 format: int64 example: - account_id: Et corporis officia sint. + account_id: Tenetur veniam repellat. has_credentials: false - id: Quidem est non tenetur dolore. - label: Cupiditate voluptatem. - org_id: Quia nulla autem ratione. - project_id: Dolores commodi aperiam voluptatibus. + id: Iste possimus ipsam perspiciatis enim id expedita. + label: Doloribus at vel necessitatibus vel. + org_id: Cumque optio impedit. + project_id: Quia voluptatem impedit. status: error - version: 874315130556833044 + version: 5803043317553025654 required: - id - project_id @@ -3380,14 +4568,14 @@ definitions: label: type: string description: Optional friendly name - example: In mollitia a magni. + example: Aut repudiandae voluptatibus odit autem. org_id: type: string description: LinkedIn organization URN id example: "208777" example: account_id: "538170226" - label: Consequatur ea deserunt tempora aut assumenda. + label: Dicta qui. org_id: "208777" required: - account_id @@ -3399,10 +4587,10 @@ definitions: access_token: type: string description: OAuth access token - example: Occaecati eum tenetur. + example: Et earum id. description: LinkedIn Ads credential. Write-only; never returned. example: - access_token: Inventore aliquid voluptatibus iste mollitia. + access_token: Voluptate dolorum id ut molestias id. required: - access_token MetaAdsConnection: @@ -3412,31 +4600,31 @@ definitions: account_id: type: string description: Provider account identifier - example: Asperiores ipsum quam. + example: Autem aliquam voluptatibus assumenda mollitia molestias eos. app_id: type: string description: Meta app ID - example: Cum rem architecto vel quibusdam vel autem. + example: Fugit eum et sint possimus omnis. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Libero atque est non praesentium. + example: Ipsa aut. label: type: string description: Optional friendly name - example: Id tenetur exercitationem aperiam incidunt. + example: Impedit vitae molestiae. page_id: type: string description: Facebook page ID - example: Qui suscipit rerum quo eligendi tempora. + example: Et consequatur vel quisquam. project_id: type: string description: Owning project - example: Saepe sit et sed qui dolorem reiciendis. + example: Non assumenda molestiae. status: type: string description: Connection status @@ -3449,18 +4637,18 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 5968285761891001183 + example: 1948493999542129316 format: int64 example: - account_id: Quibusdam aliquam consequuntur. - app_id: Deleniti earum magni quibusdam. + account_id: Vel temporibus sunt nulla esse sed. + app_id: Sed ea qui pariatur adipisci doloremque. has_credentials: false - id: Id facere maxime. - label: Maxime ut possimus expedita. - page_id: Facilis facilis aut suscipit aut odio iste. - project_id: Hic delectus. - status: active - version: 6674224742141168677 + id: Neque hic voluptatem ad similique sunt. + label: Ratione impedit recusandae dolor consequatur. + page_id: Veritatis perferendis veritatis libero. + project_id: Doloremque dicta. + status: deleted + version: 8079523500580416969 required: - id - project_id @@ -3479,20 +4667,20 @@ definitions: app_id: type: string description: Meta app ID - example: Harum provident quia distinctio ea. + example: Recusandae est perferendis eos et cumque. label: type: string description: Optional friendly name - example: Tenetur velit quaerat aut. + example: Esse dolores aut. page_id: type: string description: Facebook page ID - example: Aut et. + example: Exercitationem voluptate quia esse nihil. example: account_id: act_193556282970417 - app_id: Blanditiis atque rerum et officiis sapiente et. - label: Rem harum provident provident quia laboriosam magnam. - page_id: Hic et sed. + app_id: Laborum omnis. + label: Esse neque maiores quo aliquam. + page_id: Rem eveniet in autem neque. required: - account_id MetaAdsCredentials: @@ -3502,15 +4690,15 @@ definitions: access_token: type: string description: Meta access token - example: Itaque dolores in hic rem. + example: Voluptatem possimus veniam dignissimos consequatur. app_secret: type: string description: Meta app secret - example: Eligendi ea vel consequatur nihil. + example: Delectus enim quas id porro. description: Meta Ads credential. Write-only; never returned. example: - access_token: Vero commodi est qui asperiores velit enim. - app_secret: Expedita ut minus ut est recusandae aspernatur. + access_token: Est porro eos ipsa quia. + app_secret: Quo repudiandae. required: - access_token - app_secret @@ -3521,31 +4709,31 @@ definitions: account_id: type: string description: Provider account identifier - example: Ipsum voluptatem et quam beatae laborum omnis. + example: Modi esse voluptatibus. customer_id: type: string description: Microsoft Advertising customer ID - example: Rem suscipit similique ipsam aut quis. + example: Quo veritatis id accusamus hic. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Repudiandae voluptatem vitae iste maxime modi laborum. + example: Veniam molestias. label: type: string description: Optional friendly name - example: Magnam fugit. + example: Tenetur cumque rerum. project_id: type: string description: Owning project - example: Dignissimos necessitatibus consequuntur. + example: Accusamus quia velit commodi. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -3554,17 +4742,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 7512765750150729777 + example: 2983004504871549622 format: int64 example: - account_id: Nobis nam. - customer_id: Reiciendis pariatur quisquam. + account_id: Inventore quisquam praesentium nihil atque et nihil. + customer_id: Ad ex. has_credentials: true - id: Laudantium dolore. - label: Non quidem doloribus debitis itaque itaque dolores. - project_id: Possimus nihil qui enim ratione voluptatem eos. - status: error - version: 7616558197057713508 + id: Officia et optio sed omnis aut voluptatem. + label: Maxime et ea et. + project_id: Nisi eum omnis est. + status: inactive + version: 3453857184328446715 required: - id - project_id @@ -3579,19 +4767,19 @@ definitions: account_id: type: string description: Microsoft Advertising account ID - example: Aspernatur facere. + example: Atque reprehenderit. customer_id: type: string description: Microsoft Advertising customer ID - example: Voluptatem dicta rerum sed. + example: Eligendi incidunt accusamus excepturi. label: type: string description: Optional friendly name - example: Aspernatur ea voluptatem eum voluptas eveniet assumenda. + example: Dolore delectus ut beatae eaque. example: - account_id: Quia vero quis consequuntur id. - customer_id: Quo illum cum quod. - label: Quia possimus dolore vel qui. + account_id: Molestiae id aut debitis iure. + customer_id: Dolore est dolorem quod enim pariatur. + label: Qui est ut voluptas quod minima dolor. required: - account_id MicrosoftAdsCredentials: @@ -3601,25 +4789,25 @@ definitions: client_id: type: string description: OAuth client id - example: Ut fugiat magnam quia. + example: Occaecati veritatis. client_secret: type: string description: OAuth client secret - example: Ut tempora consequuntur cumque repudiandae omnis ut. + example: Aut architecto aliquam mollitia labore sapiente. developer_token: type: string description: Microsoft Advertising developer token - example: Est aut labore sint natus. + example: Quia mollitia. refresh_token: type: string description: OAuth refresh token - example: Distinctio beatae. + example: Tenetur non accusamus qui ex. description: Microsoft Advertising OAuth credential set. Write-only; never returned. example: - client_id: Ducimus debitis nesciunt quas quibusdam occaecati cum. - client_secret: Iste voluptatem et voluptatem ut ut. - developer_token: Quia sed omnis quaerat aut. - refresh_token: Nostrum laborum minima. + client_id: Ex et amet sunt. + client_secret: Ad quae nisi. + developer_token: Cum et ab ipsum. + refresh_token: Reiciendis enim. required: - client_id - client_secret @@ -3644,6 +4832,34 @@ definitions: required: - code - message + PlatformResult: + title: PlatformResult + type: object + properties: + campaign_id: + type: string + description: Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost. + example: Ex et alias ea asperiores quas. + error: + type: string + description: Failure reason (present when not ok) + example: Corporis dolorem laborum maiores est ea ducimus. + ok: + type: boolean + description: Whether the campaign was created (or reused) successfully + example: true + platform: + type: string + description: Platform this result is for + example: Aut quo. + example: + campaign_id: Ullam ad iusto quos et est. + error: Explicabo aliquid. + ok: false + platform: Dolorem dignissimos. + required: + - platform + - ok PreconditionFailedError: title: PreconditionFailedError type: object @@ -3689,7 +4905,7 @@ definitions: account_id: type: string description: Provider account identifier - example: Ipsam temporibus sint et necessitatibus dolorem ut. + example: Aut omnis itaque ut vero voluptatibus. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3697,19 +4913,19 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: Fugit voluptas molestias architecto dicta quidem illo. + example: Non eos. label: type: string description: Optional friendly name - example: Quia aut sed fugiat adipisci eos rerum. + example: Enim voluptatem inventore quasi. project_id: type: string description: Owning project - example: Doloremque et qui perferendis. + example: Vel aut aliquam rerum et quidem. status: type: string description: Connection status - example: active + example: error enum: - active - inactive @@ -3718,16 +4934,16 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 1442766184420227830 + example: 6330819027913820088 format: int64 example: - account_id: Voluptates neque expedita. - has_credentials: false - id: Qui sed fugiat. - label: Sit iusto. - project_id: Veniam debitis aut similique ullam temporibus qui. - status: active - version: 3889986261433875806 + account_id: Quibusdam repellendus qui numquam. + has_credentials: true + id: Voluptatibus voluptatem vero dolorem qui. + label: Vero vel. + project_id: Sequi non et. + status: inactive + version: 4305260724068280609 required: - id - project_id @@ -3746,10 +4962,10 @@ definitions: label: type: string description: Optional friendly name - example: Quisquam ut eius. + example: A voluptatem ipsa et voluptatem. example: account_id: t2_gv9wtbfa - label: Ipsum nihil cumque repellendus sint soluta. + label: Est voluptatibus placeat sint pariatur. required: - account_id RedditAdsCredentials: @@ -3759,20 +4975,20 @@ definitions: client_id: type: string description: OAuth client id - example: Sed quibusdam reiciendis qui vel. + example: Explicabo expedita et velit accusamus. client_secret: type: string description: OAuth client secret - example: Amet et eaque. + example: Deleniti placeat rerum. refresh_token: type: string description: OAuth refresh token - example: Consequatur iste sed illo adipisci sunt. + example: Nulla eos. description: Reddit Ads OAuth credential. Write-only; never returned. example: - client_id: Soluta consectetur porro quo quia incidunt. - client_secret: Aperiam et ipsam vitae. - refresh_token: Velit est quis aliquid aliquam. + client_id: Recusandae totam soluta. + client_secret: Praesentium libero. + refresh_token: Qui et assumenda quas. required: - client_id - client_secret @@ -3784,11 +5000,11 @@ definitions: account_id: type: string description: Provider account identifier - example: Vitae quae debitis sunt aperiam ex voluptatibus. + example: Qui sed voluptatibus quam quia. funding_instrument_id: type: string description: Funding instrument for the ad account - example: In fuga ut. + example: Sapiente laudantium molestiae omnis. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3796,19 +5012,19 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: Voluptate nisi natus sint. + example: Quisquam animi. label: type: string description: Optional friendly name - example: Ut dolor velit. + example: Repudiandae facilis blanditiis libero nam. project_id: type: string description: Owning project - example: Beatae aliquam quia officiis hic ea. + example: Delectus blanditiis magnam sed iure amet. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -3817,17 +5033,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 6056629402053782931 + example: 9048631027735279786 format: int64 example: - account_id: Non possimus quos voluptatem doloremque consequatur deleniti. - funding_instrument_id: Fuga illo dolorem sit ex commodi sapiente. - has_credentials: false - id: Iusto quidem laudantium. - label: Quaerat ut neque expedita nostrum. - project_id: Ratione consequuntur aut et corrupti. - status: error - version: 5145245548849296683 + account_id: Officiis aliquid explicabo eaque ab. + funding_instrument_id: Ea iusto eveniet velit sint odit sed. + has_credentials: true + id: Consequuntur perspiciatis modi. + label: Vero et atque in impedit velit omnis. + project_id: Quae sed. + status: inactive + version: 187687687190489160 required: - id - project_id @@ -3846,15 +5062,15 @@ definitions: funding_instrument_id: type: string description: Funding instrument for the ad account - example: Excepturi dolorem autem ipsa autem provident. + example: Esse voluptate optio quis quae quam. label: type: string description: Optional friendly name - example: Illum tenetur numquam. + example: Eos nemo cupiditate. example: account_id: 8r7gb - funding_instrument_id: Quis et odio tenetur optio. - label: Iure quo culpa. + funding_instrument_id: Pariatur nostrum. + label: Odio quia omnis commodi. required: - account_id TwitterAdsCredentials: @@ -3864,25 +5080,25 @@ definitions: access_token: type: string description: OAuth 1.0a access token - example: Veritatis excepturi veniam maxime incidunt asperiores voluptatem. + example: Voluptas ut vero autem consequuntur optio omnis. access_token_secret: type: string description: OAuth 1.0a access token secret - example: Nostrum dolorum iure eaque animi. + example: Pariatur omnis ut velit dolorem dolor. consumer_key: type: string description: OAuth 1.0a consumer key - example: Ut sint. + example: Illo porro velit perferendis. consumer_secret: type: string description: OAuth 1.0a consumer secret - example: Iste eveniet laudantium rerum. + example: Ipsum aut voluptas molestias unde. description: X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned. example: - access_token: Error necessitatibus et autem doloribus est. - access_token_secret: Maxime labore illo maxime provident. - consumer_key: Error laudantium. - consumer_secret: Similique odio adipisci dolore iste. + access_token: Mollitia nulla. + access_token_secret: Minus voluptatem ab delectus porro. + consumer_key: Omnis in qui. + consumer_secret: Nihil ut. required: - consumer_key - consumer_secret diff --git a/cmd/campaign-service/kodata/gen/http/openapi3.json b/cmd/campaign-service/kodata/gen/http/openapi3.json index cfa12163..e9c5187f 100644 --- a/cmd/campaign-service/kodata/gen/http/openapi3.json +++ b/cmd/campaign-service/kodata/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-campaign-service"}],"paths":{"/projects/{project_id}/connection-google-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Inventore ratione id vel iusto optio repellendus."},"example":"Rerum eveniet nam sunt."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection3"},"example":{"account_id":"Vitae qui.","has_credentials":true,"id":"Minima autem libero natus id esse.","label":"Odit eum commodi autem expedita dolorem.","login_customer_id":"In laborum vel.","project_id":"Sed adipisci eveniet et aut.","status":"active","version":4120562596096857294}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Occaecati quaerat eum quia a excepturi."},"example":"Inventore aut molestiae voluptas hic."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection2"},"example":{"account_id":"Corrupti reprehenderit et non totam perspiciatis.","has_credentials":false,"id":"Harum laborum est adipisci sint.","label":"Ipsa ex.","login_customer_id":"Perspiciatis voluptatibus architecto velit.","project_id":"Est impedit et tempore provident.","status":"error","version":6280538112072962831}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Inventore impedit unde voluptatem quia ab recusandae."},"example":"Vel omnis iure."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection4"},"example":{"account_id":"Ut commodi asperiores.","has_credentials":false,"id":"Dignissimos non ut sed.","label":"Perferendis nam voluptatibus totam.","login_customer_id":"Voluptatem similique.","project_id":"Deleniti quam.","status":"inactive","version":2550395787104322115}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Deserunt ab itaque amet numquam.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Necessitatibus quaerat minima a voluptatem ipsa et."},"example":"Pariatur perferendis explicabo expedita et velit accusamus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection3"},"example":{"account_id":"Perspiciatis saepe consequatur doloribus sequi et.","brand_kit":"Distinctio vel.","has_credentials":true,"id":"Nulla ut laboriosam.","label":"Omnis suscipit corrupti reiciendis.","portal_id":"Nihil quia omnis eaque aperiam eum.","project_id":"Rerum delectus velit.","sender_email":"Quam et voluptatibus illum dolorem.","sender_name":"Eum dolorum.","status":"active","version":3995988906528985875}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHubspotRequestBody"},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Sequi non et."},"example":"Quibusdam repellendus qui numquam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection2"},"example":{"account_id":"Voluptates iste autem veniam et enim unde.","brand_kit":"Blanditiis quidem qui iusto sed.","has_credentials":false,"id":"Nesciunt necessitatibus ex ea ullam corrupti sunt.","label":"Modi asperiores ut libero vel.","portal_id":"Rem quia consequuntur ut.","project_id":"Omnis accusantium ad.","sender_email":"Consequatur quos atque voluptates quo sit.","sender_name":"Dolor molestias ipsa dolorum est.","status":"inactive","version":1691702820919785934}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHubspotRequestBody"},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Deleniti placeat rerum."},"example":"Recusandae totam soluta."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection4"},"example":{"account_id":"Aut ratione sunt nostrum tenetur.","brand_kit":"Ab laboriosam quis sapiente ut sit.","has_credentials":true,"id":"Ipsum quaerat delectus cupiditate sit et.","label":"Magni et rerum sed voluptatum.","portal_id":"Fugit enim et sint consectetur ea minus.","project_id":"Enim est occaecati sed non dolorem distinctio.","sender_email":"Ut laborum nostrum veniam qui rerum.","sender_name":"Eum fugit repudiandae magnam ut.","status":"active","version":2378889005955898844}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialHubspotRequestBody"},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Tempora error rerum quae nihil.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ea eum."},"example":"Repudiandae quae aut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection3"},"example":{"account_id":"Quis repellat tenetur.","has_credentials":false,"id":"Et quam veritatis consequatur sed.","label":"Ipsum fuga deleniti consequatur.","org_id":"Natus laudantium quisquam.","project_id":"Tempora aut incidunt incidunt dolorem eaque et.","status":"inactive","version":2296210645404105233}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptatem doloremque ullam consequatur ut."},"example":"Ipsa magnam cum veritatis mollitia vel."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection2"},"example":{"account_id":"Totam quasi vero.","has_credentials":false,"id":"Eum quia molestiae.","label":"Est quos.","org_id":"Est est id repellat quod deleniti cupiditate.","project_id":"Rerum delectus tempora culpa.","status":"error","version":2086671994798543080}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Tempore rem quo fuga accusantium voluptas ratione."},"example":"Numquam optio possimus velit quod."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection4"},"example":{"account_id":"Ipsa rerum odit et nostrum veritatis dolores.","has_credentials":false,"id":"Aut et voluptas ut officiis.","label":"Debitis voluptatem quam occaecati.","org_id":"Et tenetur sequi dolorum porro.","project_id":"In autem tempore ut esse.","status":"deleted","version":7961829707957443728}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialLinkedinAdsRequestBody"},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Dolorum voluptates placeat rerum.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Vel necessitatibus vel voluptates tenetur veniam repellat."},"example":"Aut repudiandae voluptatibus odit autem."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection3"},"example":{"account_id":"Voluptatem cupiditate voluptas aliquam facilis iste sed.","app_id":"A sed exercitationem ut.","has_credentials":false,"id":"Expedita unde molestiae.","label":"Velit ullam.","page_id":"Voluptatum a.","project_id":"Voluptas dolor et incidunt pariatur quod odit.","status":"active","version":6376430019451136769}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Perspiciatis quam iste possimus ipsam."},"example":"Eveniet quia voluptatem impedit cumque doloribus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection2"},"example":{"account_id":"Fuga ut expedita voluptatum sit.","app_id":"Quia ut atque.","has_credentials":true,"id":"Quis perspiciatis.","label":"Similique vel nobis non molestias.","page_id":"Velit et.","project_id":"Nesciunt unde ullam occaecati maxime.","status":"active","version":8123871071866488705}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Dicta qui."},"example":"Voluptate dolorum id ut molestias id."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection4"},"example":{"account_id":"Quos quo eum dignissimos iure praesentium.","app_id":"Voluptas ratione delectus ut adipisci.","has_credentials":false,"id":"Doloremque itaque eius magni autem.","label":"Ex tempore.","page_id":"Qui mollitia perferendis eveniet voluptas est et.","project_id":"Ullam tenetur quia totam magni sunt repellat.","status":"inactive","version":7772855560046758577}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialMetaAdsRequestBody"},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Aliquam animi unde.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Non eos."},"example":"Enim voluptatem inventore quasi."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection3"},"example":{"account_id":"Optio dolorem omnis.","customer_id":"Rerum adipisci minima quibusdam autem accusantium.","has_credentials":false,"id":"Veniam eum ut libero quibusdam debitis.","label":"Commodi quam voluptates.","project_id":"Ut consectetur.","status":"deleted","version":6203614951491514230}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Delectus enim quas id porro."},"example":"Quo repudiandae."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection2"},"example":{"account_id":"Exercitationem rerum iste ducimus.","customer_id":"Quidem velit est explicabo est sed.","has_credentials":true,"id":"Modi rerum sint.","label":"Dolorem dolor fugiat quia.","project_id":"Labore iste voluptates quos.","status":"inactive","version":8271015532862321776}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut omnis itaque ut vero voluptatibus."},"example":"Vero dolorem qui."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection4"},"example":{"account_id":"Maxime fugiat illo.","customer_id":"Minus est ut.","has_credentials":false,"id":"Tempora recusandae.","label":"Reiciendis architecto atque maxime soluta dolorem ducimus.","project_id":"Itaque harum.","status":"deleted","version":6177609042943350629}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Ullam fugiat.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Autem aliquam voluptatibus assumenda mollitia molestias eos."},"example":"Fugit eum et sint possimus omnis."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection3"},"example":{"account_id":"Amet iusto provident sequi est.","has_credentials":true,"id":"Voluptas non accusantium.","label":"Sit dolorum excepturi quibusdam.","project_id":"Architecto consequatur enim magnam eius voluptas voluptatibus.","status":"deleted","version":1322493338651594921}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ipsa aut."},"example":"Impedit vitae molestiae."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection2"},"example":{"account_id":"Ut ea alias.","has_credentials":false,"id":"Explicabo cupiditate rerum.","label":"Hic vero et.","project_id":"Incidunt ipsum est perspiciatis soluta voluptas est.","status":"inactive","version":347677279926224703}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Neque hic voluptatem ad similique sunt."},"example":"Ratione impedit recusandae dolor consequatur."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection4"},"example":{"account_id":"Saepe pariatur.","has_credentials":true,"id":"Vero quam.","label":"Eveniet soluta adipisci.","project_id":"Neque tempore voluptatibus quo quod nesciunt.","status":"active","version":779913646680513111}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialRedditAdsRequestBody"},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Repudiandae veniam soluta alias natus aut.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Enim esse dolores."},"example":"Est perferendis eos et cumque nulla."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection3"},"example":{"account_id":"Et tempore.","funding_instrument_id":"Est et id laudantium magnam ratione.","has_credentials":false,"id":"Ipsum deserunt ut.","label":"Ut nihil quo tenetur doloribus fugiat natus.","project_id":"Sed ea.","status":"error","version":6360932954176398686}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Vel temporibus sunt nulla esse sed."},"example":"Facere sed ea qui pariatur adipisci."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection2"},"example":{"account_id":"Totam sequi.","funding_instrument_id":"Labore ut iusto dolores.","has_credentials":false,"id":"Dolor ut.","label":"Porro sed minus illo rerum ut.","project_id":"Veniam maiores dolorum sit et.","status":"active","version":5887915076445143491}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Neque maiores quo aliquam in rem."},"example":"Voluptatem possimus veniam dignissimos consequatur."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection4"},"example":{"account_id":"Est dolorem eos amet dolorem sed.","funding_instrument_id":"Quia porro.","has_credentials":false,"id":"Ab nobis fuga atque inventore fugiat.","label":"Quod blanditiis et.","project_id":"Laboriosam maxime facere culpa modi.","status":"error","version":796228352115418506}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialTwitterAdsRequestBody"},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Quo aliquid nobis.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"ConflictError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Rerum sunt eum et qui."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":true}},"example":{"message":"Ut quia harum.","ok":false},"required":["ok"]},"CreateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["config","credentials"]},"CreateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"},"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["config","credentials"]},"CreateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["config","credentials"]},"CreateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["config","credentials"]},"CreateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["config","credentials"]},"CreateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["config","credentials"]},"CreateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["config","credentials"]},"GoogleAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptatem sed quia excepturi."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptas ullam dolores rem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Labore laudantium."},"label":{"type":"string","description":"Optional friendly name","example":"Eos voluptatibus."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Officia quidem non qui voluptates natus cum."},"project_id":{"type":"string","description":"Owning project","example":"Facilis reiciendis dignissimos consequatur iste qui minima."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1885605882290356239,"format":"int64"}},"example":{"account_id":"Iusto velit quo voluptatibus modi reiciendis aliquam.","etag":"Sunt recusandae similique.","has_credentials":false,"id":"Autem recusandae totam.","label":"Odit necessitatibus veritatis amet ut rerum quidem.","login_customer_id":"Iusto quia.","project_id":"Rerum nisi.","status":"error","version":4043658872882310311},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"GoogleAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Error ipsam maiores et maiores laudantium aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aliquid harum quis."},"label":{"type":"string","description":"Optional friendly name","example":"Mollitia odio et dolor et consectetur."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Sint inventore iste iure ut."},"project_id":{"type":"string","description":"Owning project","example":"Rerum eaque eius est rerum."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1681601347963415661,"format":"int64"}},"example":{"account_id":"Ipsa et dolore.","has_credentials":true,"id":"Facilis suscipit.","label":"Ad inventore voluptatum eligendi quasi.","login_customer_id":"Placeat unde distinctio ducimus qui.","project_id":"Porro voluptate.","status":"inactive","version":1820304569870851201},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nihil corrupti repellat eius."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Dignissimos non sed omnis officiis et."},"label":{"type":"string","description":"Optional friendly name","example":"Magni repudiandae."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Delectus quam et eos."},"project_id":{"type":"string","description":"Owning project","example":"Laboriosam qui id enim."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8134511901185890068,"format":"int64"}},"example":{"account_id":"Illo vitae occaecati omnis.","has_credentials":false,"id":"Rem aut fuga.","label":"Cupiditate ut illo dolor quaerat totam.","login_customer_id":"Explicabo possimus consequatur.","project_id":"Est dolor ut corporis.","status":"active","version":299946558233755685},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"A necessitatibus molestiae."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quos hic doloribus."},"label":{"type":"string","description":"Optional friendly name","example":"Eum et sint corrupti eos."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Dolorem qui explicabo consequuntur repellat non suscipit."},"project_id":{"type":"string","description":"Owning project","example":"Neque autem ex eos."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7747776820204091337,"format":"int64"}},"example":{"account_id":"Sit est dolores est et.","has_credentials":false,"id":"Sit tempore.","label":"Ut quo iure.","login_customer_id":"Velit atque laudantium ullam rerum.","project_id":"Magnam ut similique ea minima magnam.","status":"deleted","version":7309957715046801903},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ut aut."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Sit similique ut minima ut."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Voluptatum officia quod error amet quia inventore."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Voluptas expedita omnis ex."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Non ut voluptas et accusamus natus id.","client_secret":"Et officia sequi et quam.","developer_token":"Vero quisquam.","refresh_token":"Quia enim illo et eligendi."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Delectus voluptatem iste nisi in."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Nostrum corporis quasi magnam."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Dolor occaecati earum iusto rerum sit cupiditate."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Nam quia aperiam sunt est."},"label":{"type":"string","description":"Optional friendly name","example":"Natus expedita aspernatur consequuntur quod ducimus molestias."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Iste qui rerum est sed sed similique."},"project_id":{"type":"string","description":"Owning project","example":"Libero ut est rerum est occaecati et."},"sender_email":{"type":"string","description":"Default sender address","example":"Ea maiores quidem molestiae deleniti omnis."},"sender_name":{"type":"string","description":"Default sender name","example":"Expedita voluptatum commodi fuga ipsa."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3681253484013323435,"format":"int64"}},"example":{"account_id":"Neque facilis est qui possimus eligendi.","brand_kit":"Voluptas eius error autem est est distinctio.","etag":"Et nobis voluptas dignissimos exercitationem illum.","has_credentials":false,"id":"Qui quidem exercitationem.","label":"Sed numquam necessitatibus laudantium est ut.","portal_id":"Quisquam ex est facilis qui ducimus.","project_id":"Ipsa quo vel architecto quas eligendi impedit.","sender_email":"Ut culpa excepturi.","sender_name":"Officiis quia quisquam corrupti.","status":"inactive","version":8080806383587730069},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"HubspotConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Magni omnis."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Perferendis culpa voluptatem nostrum eaque deleniti."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Esse voluptate."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptatum et impedit quod."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Est repellat."},"project_id":{"type":"string","description":"Owning project","example":"Consectetur et culpa quaerat vel."},"sender_email":{"type":"string","description":"Default sender address","example":"Esse sit id incidunt mollitia non sed."},"sender_name":{"type":"string","description":"Default sender name","example":"Minima est distinctio accusamus."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7333781901734102627,"format":"int64"}},"example":{"account_id":"Aliquam animi.","brand_kit":"Eos iusto fugit.","has_credentials":true,"id":"Quo nostrum quia.","label":"Velit quis.","portal_id":"Error dolor placeat et.","project_id":"Ut nobis.","sender_email":"Et esse fugiat aliquid aspernatur voluptatem in.","sender_name":"Consequatur et dolorem.","status":"error","version":2888074467672743464},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptas omnis quidem dolor ipsum voluptas sed."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Tempora dolor quis totam hic quibusdam eveniet."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Asperiores quidem."},"label":{"type":"string","description":"Optional friendly name","example":"Nesciunt adipisci odit corrupti laudantium consequatur hic."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Dolorem necessitatibus neque consectetur occaecati earum."},"project_id":{"type":"string","description":"Owning project","example":"Est nostrum atque."},"sender_email":{"type":"string","description":"Default sender address","example":"Ut sint non eum expedita non."},"sender_name":{"type":"string","description":"Default sender name","example":"Sit voluptatem ad ab."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3980984224261423037,"format":"int64"}},"example":{"account_id":"Quidem omnis totam eos non molestias.","brand_kit":"Ducimus id sint eius aut.","has_credentials":false,"id":"Doloribus voluptas mollitia doloremque totam.","label":"Quia doloremque commodi rerum.","portal_id":"Recusandae illum accusamus debitis.","project_id":"Veniam et ducimus quos amet.","sender_email":"Praesentium sint laudantium error.","sender_name":"Nihil iure officiis nemo quia.","status":"inactive","version":6150421422111226538},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ab quo ullam ad."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Aperiam facere voluptas quis nihil."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Et quia ex et alias ea."},"label":{"type":"string","description":"Optional friendly name","example":"Laborum maiores est ea ducimus ipsum dolorem."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Placeat explicabo."},"project_id":{"type":"string","description":"Owning project","example":"Quas quis corporis."},"sender_email":{"type":"string","description":"Default sender address","example":"Rerum tenetur ut voluptatem quo."},"sender_name":{"type":"string","description":"Default sender name","example":"Iste aut cum voluptatem qui et."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3448886829732168782,"format":"int64"}},"example":{"account_id":"Vero ut et neque soluta distinctio voluptas.","brand_kit":"Voluptatem incidunt cum.","has_credentials":false,"id":"Rem asperiores eligendi vero.","label":"Facere quas qui aut rerum.","portal_id":"Fugit eum.","project_id":"Voluptatem fugiat et.","sender_email":"Necessitatibus voluptatem incidunt et totam esse rerum.","sender_name":"Dolorem delectus.","status":"active","version":5717670313645740058},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Quis quis reprehenderit non quae omnis qui."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Voluptatibus deserunt."},"label":{"type":"string","description":"Optional friendly name","example":"At odio incidunt distinctio sint qui."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Cumque saepe sint quidem odio ab."},"sender_email":{"type":"string","description":"Default sender address","example":"Aut est."},"sender_name":{"type":"string","description":"Default sender name","example":"Error pariatur consequatur esse."}},"example":{"account_id":"Et eligendi et numquam dolorum consequatur in.","brand_kit":"Non sequi error assumenda quam deleniti et.","label":"Necessitatibus eos.","portal_id":"Et eum est molestiae necessitatibus magni.","sender_email":"Molestiae praesentium perferendis deserunt ipsum dolorum.","sender_name":"Error nulla fuga fugit repellendus et accusamus."},"required":["account_id"]},"HubspotCredentials":{"type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Dolor officia eligendi."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Provident dicta temporibus debitis."},"required":["private_app_token"]},"InternalServerError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"LinkedinAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Cumque molestiae eos rem aut."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Non adipisci rerum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Adipisci quaerat est nobis."},"label":{"type":"string","description":"Optional friendly name","example":"Et veritatis quisquam sed ratione et."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Placeat ea odio ea eos."},"project_id":{"type":"string","description":"Owning project","example":"Fugit non fuga rem adipisci."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7868855978793741277,"format":"int64"}},"example":{"account_id":"Nobis itaque est enim magnam pariatur.","etag":"Praesentium ipsam officiis cum ipsum recusandae.","has_credentials":true,"id":"Ipsum et delectus dolorum eveniet.","label":"Maxime maxime porro enim.","org_id":"Adipisci ullam pariatur ex voluptas.","project_id":"Alias ea quibusdam.","status":"active","version":5465298508990820984},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"LinkedinAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolorum deserunt."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Rerum adipisci."},"label":{"type":"string","description":"Optional friendly name","example":"Non laudantium sunt minima repudiandae illo harum."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Eos nihil ipsam ex odio totam."},"project_id":{"type":"string","description":"Owning project","example":"Enim accusantium labore esse numquam."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2440284349670140496,"format":"int64"}},"example":{"account_id":"Omnis consequatur.","has_credentials":true,"id":"Deleniti placeat fuga aut saepe.","label":"Ut quis ut accusantium.","org_id":"Aut autem sint minus voluptatum.","project_id":"Reiciendis pariatur repellat.","status":"error","version":4719926195195381744},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quo veritatis quis delectus est."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Tempore quae."},"label":{"type":"string","description":"Optional friendly name","example":"Dolores tenetur ut illum."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Expedita accusantium vel voluptate."},"project_id":{"type":"string","description":"Owning project","example":"Consequatur dolore unde illum."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1676862666948614995,"format":"int64"}},"example":{"account_id":"Magni consequatur dignissimos itaque quia.","has_credentials":true,"id":"Repellat assumenda nesciunt distinctio eaque aliquam.","label":"Et reprehenderit dignissimos.","org_id":"Et voluptatem deserunt consequuntur omnis sed dolore.","project_id":"Veniam consectetur deserunt in hic dolorum facere.","status":"inactive","version":7072791787806691868},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Assumenda dignissimos beatae vitae aut ea dolor."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Voluptatibus quod necessitatibus non iste ad rem."},"label":{"type":"string","description":"Optional friendly name","example":"Harum corporis deleniti doloribus."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Sit architecto ut unde."},"project_id":{"type":"string","description":"Owning project","example":"Sunt qui cupiditate ullam ab suscipit et."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6055913800302101668,"format":"int64"}},"example":{"account_id":"Praesentium tenetur quas at ut necessitatibus dolor.","has_credentials":false,"id":"Sapiente possimus.","label":"Eaque voluptate reprehenderit sed voluptatem sed illo.","org_id":"Nihil id unde veritatis officiis aut fugiat.","project_id":"Voluptates atque.","status":"error","version":446959638978499075},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Cumque totam nulla aut reprehenderit."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Illum rerum error incidunt.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Aspernatur possimus."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Repellendus quo quidem."},"required":["access_token"]},"MetaAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Iusto ad animi eos doloribus autem corrupti."},"app_id":{"type":"string","description":"Meta app ID","example":"Laborum eum et quidem."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Suscipit quas sunt eos nulla."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Facere exercitationem et exercitationem."},"label":{"type":"string","description":"Optional friendly name","example":"Rerum quidem accusamus et provident provident omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Perferendis qui maiores aut reiciendis."},"project_id":{"type":"string","description":"Owning project","example":"Eaque deleniti."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1750381436080099913,"format":"int64"}},"example":{"account_id":"Ad non.","app_id":"Est voluptas sequi.","etag":"Possimus consequatur est unde.","has_credentials":false,"id":"Quasi quis ab deserunt quia magnam.","label":"Quod modi voluptas dolores commodi dicta.","page_id":"Et laudantium.","project_id":"Minus et aut consequatur non cumque.","status":"active","version":3589524561643771783},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MetaAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nostrum tempore."},"app_id":{"type":"string","description":"Meta app ID","example":"Rerum explicabo."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ut commodi et voluptas tenetur nobis facere."},"label":{"type":"string","description":"Optional friendly name","example":"Blanditiis mollitia dolores illo nostrum magnam."},"page_id":{"type":"string","description":"Facebook page ID","example":"Deserunt repellendus corrupti itaque quo sit."},"project_id":{"type":"string","description":"Owning project","example":"Quo corporis recusandae dicta quibusdam."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6534648276901202778,"format":"int64"}},"example":{"account_id":"Perspiciatis porro.","app_id":"Deserunt quod.","has_credentials":false,"id":"Eum et ipsum expedita reprehenderit eos.","label":"Aperiam et nihil.","page_id":"Non rem error.","project_id":"Dolorum ut architecto deleniti nostrum.","status":"deleted","version":2783740423386270532},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptate necessitatibus enim et et itaque."},"app_id":{"type":"string","description":"Meta app ID","example":"Nihil aut rerum est vel."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aperiam quos perferendis nisi blanditiis eveniet."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Rerum quis et modi optio labore omnis."},"project_id":{"type":"string","description":"Owning project","example":"Ea enim ut corporis dolorum consequatur eius."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":9014435567821091487,"format":"int64"}},"example":{"account_id":"Dolor aut assumenda fugiat.","app_id":"Aut quas suscipit eos.","has_credentials":true,"id":"Rem ab corporis.","label":"Porro et.","page_id":"Inventore alias error aut maxime dicta.","project_id":"Non dolorem.","status":"deleted","version":5862072609653585869},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolor et corrupti voluptas neque vitae."},"app_id":{"type":"string","description":"Meta app ID","example":"Rerum numquam amet deserunt rem quia."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsa rerum velit et impedit."},"label":{"type":"string","description":"Optional friendly name","example":"Velit quam omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Mollitia est assumenda aut harum."},"project_id":{"type":"string","description":"Owning project","example":"Quaerat labore dolor."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1514261665871912761,"format":"int64"}},"example":{"account_id":"Deleniti deserunt labore.","app_id":"Molestiae deserunt voluptates.","has_credentials":true,"id":"Ipsam dolores et et qui ut.","label":"Velit esse voluptate eum laboriosam distinctio.","page_id":"Adipisci quo.","project_id":"Numquam omnis ullam facilis ab sapiente.","status":"error","version":3954861542185537606},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Eius sed qui deserunt perspiciatis nihil."},"label":{"type":"string","description":"Optional friendly name","example":"Et eveniet."},"page_id":{"type":"string","description":"Facebook page ID","example":"Corporis ut non."}},"example":{"account_id":"act_193556282970417","app_id":"Aut hic.","label":"Mollitia tempora minima voluptas.","page_id":"Facere aut facilis porro dolor ipsa."},"required":["account_id"]},"MetaAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Aperiam voluptates."},"app_secret":{"type":"string","description":"Meta app secret","example":"Vero ducimus."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Recusandae aut perferendis.","app_secret":"Est eaque ipsam harum nisi nobis."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Et nulla officia vitae."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Et sunt quis dicta voluptate aut in."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Ut asperiores quo aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Perspiciatis id."},"label":{"type":"string","description":"Optional friendly name","example":"Nihil nihil quae porro qui rem autem."},"project_id":{"type":"string","description":"Owning project","example":"Ut non error aut et fuga."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2431299518347433245,"format":"int64"}},"example":{"account_id":"Id temporibus et provident maiores neque quis.","customer_id":"Sunt assumenda.","etag":"Eligendi ut.","has_credentials":false,"id":"Provident nobis soluta culpa vitae.","label":"Laudantium amet veritatis rerum itaque consequatur laboriosam.","project_id":"Excepturi deleniti voluptas aperiam vel.","status":"error","version":4162198444433663334},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MicrosoftAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Earum minus autem omnis qui debitis eum."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Quidem necessitatibus voluptatem consequatur sit."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Esse officia explicabo dolor deserunt aspernatur."},"label":{"type":"string","description":"Optional friendly name","example":"Cupiditate mollitia hic vel ut."},"project_id":{"type":"string","description":"Owning project","example":"Aspernatur illo accusamus."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3699306814781450025,"format":"int64"}},"example":{"account_id":"Sed corrupti sit.","customer_id":"Consectetur tenetur eum.","has_credentials":true,"id":"Et vel beatae molestiae fuga.","label":"Aut ut.","project_id":"Vitae consequatur eos.","status":"active","version":436879921687162189},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Qui impedit temporibus eveniet eos omnis cumque."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Natus et unde."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aut ut sit nisi architecto."},"label":{"type":"string","description":"Optional friendly name","example":"Praesentium voluptate."},"project_id":{"type":"string","description":"Owning project","example":"Rerum et recusandae possimus quis."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1288874109548033633,"format":"int64"}},"example":{"account_id":"Hic est quia pariatur odit ullam.","customer_id":"Sed quo perferendis nemo ut quisquam.","has_credentials":true,"id":"Explicabo repudiandae aut reiciendis recusandae molestias.","label":"Quod impedit accusamus incidunt minus veritatis enim.","project_id":"Vel inventore aliquid aut non eligendi accusamus.","status":"error","version":2175885598856983154},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Hic rerum accusamus velit error."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Dolorem deserunt quasi in aut nam rerum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Repellendus quos magnam."},"label":{"type":"string","description":"Optional friendly name","example":"Pariatur repellat vel."},"project_id":{"type":"string","description":"Owning project","example":"In id voluptatem nihil quaerat."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8411140861799189617,"format":"int64"}},"example":{"account_id":"Sint est sed.","customer_id":"Illum quisquam ex ipsa.","has_credentials":true,"id":"Distinctio magnam.","label":"Quia magni aut nisi est.","project_id":"Architecto deserunt vero ducimus distinctio.","status":"active","version":6452524533918462281},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Ex quo praesentium voluptas."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Est officia et."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptatem mollitia omnis doloribus qui consequatur et."}},"example":{"account_id":"Eos et architecto voluptas.","customer_id":"Vel natus provident.","label":"Numquam accusamus."},"required":["account_id"]},"MicrosoftAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Architecto itaque doloribus voluptatem consequatur quis fugiat."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Recusandae qui culpa."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Sed rerum dicta distinctio voluptas ut ut."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Accusamus et atque enim."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Fuga nostrum velit hic excepturi omnis.","client_secret":"Distinctio quos ut unde et atque.","developer_token":"In recusandae maiores et voluptatem.","refresh_token":"Odio labore pariatur nobis."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PreconditionFailedError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolores assumenda accusantium mollitia fugiat dolore similique."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Molestiae quos cum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Cum molestias fuga voluptatibus quis."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis aliquid rerum consequatur officiis velit."},"project_id":{"type":"string","description":"Owning project","example":"Aut rerum ut blanditiis dolor."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4135215091203906464,"format":"int64"}},"example":{"account_id":"Natus placeat esse est in deserunt aliquid.","etag":"Sed et odio sunt totam incidunt.","has_credentials":false,"id":"Architecto doloremque aperiam doloribus aut qui.","label":"Distinctio ipsum ab.","project_id":"Placeat cumque iure veritatis cumque nisi.","status":"error","version":6763438623066254599},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"RedditAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Assumenda harum eos eveniet ut et laboriosam."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Culpa saepe necessitatibus."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptas aperiam."},"project_id":{"type":"string","description":"Owning project","example":"Dolores enim."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6456486653687697700,"format":"int64"}},"example":{"account_id":"Cumque quae.","has_credentials":false,"id":"Qui eaque ut laudantium.","label":"Laborum aspernatur voluptate itaque fugit.","project_id":"Ratione id.","status":"deleted","version":5238309974693293664},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolore velit pariatur non assumenda perspiciatis dolor."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Odit consequatur."},"label":{"type":"string","description":"Optional friendly name","example":"Ut ullam."},"project_id":{"type":"string","description":"Owning project","example":"Vero soluta omnis eius."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3837313741085790100,"format":"int64"}},"example":{"account_id":"Animi molestiae aut fugit alias nisi qui.","has_credentials":false,"id":"Molestiae ea ullam minima voluptatem vel.","label":"Beatae quia minus vero at ut.","project_id":"Dolore repudiandae necessitatibus dolorem.","status":"inactive","version":8129543513726361746},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Autem iste ea in ipsa."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Deserunt dolorum nihil."},"label":{"type":"string","description":"Optional friendly name","example":"Odio odio sed minus quia quia ea."},"project_id":{"type":"string","description":"Owning project","example":"Doloremque explicabo ut."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4150548058954901958,"format":"int64"}},"example":{"account_id":"Quibusdam consequuntur dolorem fugiat.","has_credentials":true,"id":"Perspiciatis magni tempore sunt itaque.","label":"Sit beatae ut modi voluptatum veniam.","project_id":"Ut ex.","status":"error","version":5881477690177318757},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Qui molestias."}},"example":{"account_id":"t2_gv9wtbfa","label":"Molestiae minus dolor accusantium."},"required":["account_id"]},"RedditAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Numquam quidem qui."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Laboriosam possimus dolor facere velit dolores."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Alias ad voluptatem accusantium tempore impedit."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Et expedita itaque quos.","client_secret":"Quos accusantium fugit.","refresh_token":"Excepturi saepe sit vel perspiciatis neque."},"required":["client_id","client_secret","refresh_token"]},"ServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"SetCredentialGoogleAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["credentials"]},"SetCredentialHubspotRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["credentials"]},"SetCredentialLinkedinAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["credentials"]},"SetCredentialMetaAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["credentials"]},"SetCredentialRedditAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["credentials"]},"SetCredentialTwitterAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["credentials"]},"TwitterAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quisquam incidunt qui."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Quia exercitationem consequatur velit."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Quia culpa iste id sit incidunt."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Mollitia atque atque dolores accusantium non dolor."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis labore dicta."},"project_id":{"type":"string","description":"Owning project","example":"Enim qui officia id necessitatibus mollitia soluta."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2620236536617279883,"format":"int64"}},"example":{"account_id":"Accusantium enim temporibus dolor ipsam.","etag":"Aut ducimus dolores.","funding_instrument_id":"Dolorum minus voluptatem.","has_credentials":false,"id":"Officiis est.","label":"Tempore nemo illum ut maiores.","project_id":"Qui quisquam voluptatibus sint quia.","status":"error","version":6092912427529457622},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"TwitterAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Delectus ex aut quia."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Corporis consequatur voluptatem voluptates."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Soluta odit odio praesentium perferendis eveniet sint."},"label":{"type":"string","description":"Optional friendly name","example":"Temporibus quis iste eaque ratione debitis nam."},"project_id":{"type":"string","description":"Owning project","example":"Quam dicta voluptate consequuntur voluptatem est."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":629084196159484927,"format":"int64"}},"example":{"account_id":"Velit fuga quos.","funding_instrument_id":"Sed aut quia nihil est quia.","has_credentials":true,"id":"Reprehenderit provident repudiandae sit.","label":"Ut sunt aliquam.","project_id":"Aut aut eos magnam quisquam et.","status":"active","version":2879571083312014203},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Velit laudantium error."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Beatae harum similique corporis id eum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fugiat est itaque."},"label":{"type":"string","description":"Optional friendly name","example":"Culpa est possimus dolores dignissimos."},"project_id":{"type":"string","description":"Owning project","example":"Fugiat aut dicta libero."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7144064222780250488,"format":"int64"}},"example":{"account_id":"Enim vitae officia dolorem odio.","funding_instrument_id":"Excepturi minima illo dolores.","has_credentials":false,"id":"Et itaque rem eos et deleniti dolores.","label":"Facere qui omnis.","project_id":"Facilis qui libero.","status":"active","version":6722562537039921847},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Non quo necessitatibus."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Laudantium nostrum earum dolores sit."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"A sint quod sunt quae et."},"label":{"type":"string","description":"Optional friendly name","example":"Ut mollitia nihil ducimus."},"project_id":{"type":"string","description":"Owning project","example":"Iure quis."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3532519562043610511,"format":"int64"}},"example":{"account_id":"At fugit dolor.","funding_instrument_id":"Non exercitationem modi possimus consequatur non.","has_credentials":false,"id":"Exercitationem accusantium nihil.","label":"Amet omnis.","project_id":"Quod alias ipsum voluptates pariatur magni.","status":"error","version":5818865715898604715},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Ut mollitia accusantium dignissimos."},"label":{"type":"string","description":"Optional friendly name","example":"Fugit provident officia officiis."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Rerum ut necessitatibus illum ratione.","label":"Consequatur qui molestiae velit ratione nesciunt et."},"required":["account_id"]},"TwitterAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"At perspiciatis quisquam perferendis."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Ut alias quis nihil dolore suscipit reprehenderit."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Id ipsa perspiciatis sunt ut velit."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Aut mollitia aut molestiae exercitationem et."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Et aut consequatur inventore enim reiciendis.","access_token_secret":"Neque ut rerum facilis quia molestiae ratione.","consumer_key":"Aut animi laudantium ullam.","consumer_secret":"Autem et id laboriosam nihil ea."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]},"UpdateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"UpdateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}},"required":["config"]},"UpdateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}},"required":["config"]},"UpdateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}},"required":["config"]},"UpdateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}},"required":["config"]},"UpdateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}},"required":["config"]},"UpdateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}},"required":["config"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"JWT issued by Heimdall; audience is this service.","scheme":"bearer"}}},"tags":[{"name":"lfx-v2-campaign-service-svc","description":"The campaign service supports Marketing Operations campaign creation and management."},{"name":"lfx-v2-campaign-service-connections","description":"Manage a project's singleton, per-provider ad-platform connections."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-campaign-service"}],"paths":{"/projects/{project_id}/briefs":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-brief lfx-v2-campaign-service-briefs","description":"Create a brief.","operationId":"lfx-v2-campaign-service-briefs#create-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBriefRequestBody"},"example":{"brief":{"copy":"Blanditiis quidem qui iusto sed.","event_details":"Dolor molestias ipsa dolorum est.","event_slug":"Accusantium ad.","keywords":"Vel fuga recusandae.","platforms":["Iste autem veniam et enim unde.","Facere quam.","Rem quia consequuntur ut.","Consequatur quos atque voluptates quo sit."],"program_type":"membership","targeting":"Nulla ut laboriosam.","url":"Modi asperiores ut libero vel."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aliquid qui omnis et."},"example":"Asperiores recusandae deserunt ratione eius culpa sit."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief2"},"example":{"copy":"Magni et rerum sed voluptatum.","event_details":"Enim est occaecati sed non dolorem distinctio.","event_slug":"Aperiam eum.","id":"Omnis suscipit corrupti reiciendis.","keywords":"Aut ratione sunt nostrum tenetur.","platforms":["Dolorum aut distinctio vel.","Veniam velit sequi sit impedit dolorum voluptas.","Ipsum quaerat delectus cupiditate sit et."],"program_type":"Beatae soluta molestiae nihil quia omnis.","project_id":"Perspiciatis saepe consequatur doloribus sequi et.","status":"draft","targeting":"Dolorem cum.","url":"Quam et voluptatibus illum dolorem.","version":1135469282733514180}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}":{"delete":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"delete-brief lfx-v2-campaign-service-briefs","description":"Archive a brief (soft delete).","operationId":"lfx-v2-campaign-service-briefs#delete-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"b6261fa8-da85-4670-81bb-f0aa10d65871","format":"uuid"},"example":"4d147ddb-fca6-489e-a2a8-78921eadc560"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-brief lfx-v2-campaign-service-briefs","description":"Get a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"52099480-3594-4323-92b0-9a8e5a8fedcc","format":"uuid"},"example":"6d67841a-f263-4343-acd9-5ea22a27e47d"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Libero repudiandae aliquid ex."},"example":"Eveniet suscipit odio ratione."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief3"},"example":{"copy":"Dolores commodi aperiam voluptatibus.","event_details":"Omnis quidem est non tenetur dolore.","event_slug":"Distinctio ipsa nihil ad hic repellendus nobis.","id":"Harum et minus voluptas.","keywords":"Cupiditate voluptatem.","platforms":["Cupiditate aliquam vel blanditiis sunt ut.","Aperiam quo sint dolorum vel molestiae."],"program_type":"Porro ab id necessitatibus consequatur natus tempora.","project_id":"Debitis labore aut.","status":"approved","targeting":"Et corporis officia sint.","url":"Accusamus vero et voluptas accusamus sunt.","version":1623908575818221398}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-brief lfx-v2-campaign-service-briefs","description":"Replace a brief (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"7b75bd11-b6e0-441e-99e4-25f506ecfdd1","format":"uuid"},"example":"913052d6-1036-4a14-9f9a-107c3b258bb8"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBriefRequestBody"},"example":{"brief":{"copy":"Blanditiis quidem qui iusto sed.","event_details":"Dolor molestias ipsa dolorum est.","event_slug":"Accusantium ad.","keywords":"Vel fuga recusandae.","platforms":["Iste autem veniam et enim unde.","Facere quam.","Rem quia consequuntur ut.","Consequatur quos atque voluptates quo sit."],"program_type":"membership","targeting":"Nulla ut laboriosam.","url":"Modi asperiores ut libero vel."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ut aperiam eveniet nihil qui ea."},"example":"Id aut praesentium maiores unde quo culpa."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief4"},"example":{"copy":"Doloremque et qui perferendis.","event_details":"Fugit voluptas molestias architecto dicta quidem illo.","event_slug":"Sapiente et et itaque dolores in.","id":"Provident quia laboriosam magnam unde.","keywords":"Quia aut sed fugiat adipisci eos rerum.","platforms":["Nihil explicabo vero.","Est qui asperiores velit enim.","Expedita ut minus ut est recusandae aspernatur."],"program_type":"Rerum et.","project_id":"Et sed nobis blanditiis.","status":"approved","targeting":"Ipsam temporibus sint et necessitatibus dolorem ut.","url":"Rem dolores eligendi ea.","version":6226287149251328788}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/approve":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"approve-brief lfx-v2-campaign-service-briefs","description":"Approve a brief for campaign creation.","operationId":"lfx-v2-campaign-service-briefs#approve-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"25175f34-92c0-434d-a72f-1a9c500ccdda","format":"uuid"},"example":"78a49884-958d-4ef7-99b9-e60dab3c1338"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Qui inventore."},"example":"Sunt cum ut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief5"},"example":{"copy":"Similique odio adipisci dolore iste.","event_details":"Error laudantium.","event_slug":"Dolorem autem ipsa autem provident est.","id":"Pariatur voluptas odit fuga illo.","keywords":"Error necessitatibus et autem doloribus est.","platforms":["Et odio tenetur optio in ut sint.","Iste eveniet laudantium rerum.","Veritatis excepturi veniam maxime incidunt asperiores voluptatem.","Nostrum dolorum iure eaque animi."],"program_type":"Numquam quia.","project_id":"Sit ex commodi sapiente a illum.","status":"archived","targeting":"Maxime labore illo maxime provident.","url":"Quo culpa.","version":8076581276173981715}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-campaigns lfx-v2-campaign-service-briefs","description":"Create campaigns across the selected platforms (async -\u003e job).","operationId":"lfx-v2-campaign-service-briefs#create-campaigns","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"d76e3514-7f04-404b-85c1-c9dd589109e7","format":"uuid"},"example":"cc88b92c-5d67-435b-9340-3a62f5a9bab0"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignsRequestBody"},"example":{"input":{"config":"Facere et exercitationem.","platforms":["microsoft-ads","twitter-ads","google-ads"]}}}}},"responses":{"202":{"description":"Accepted response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCreateResponse"},"example":{"job_id":"Et magni adipisci magnam veniam.","platforms":["Odio et corporis.","Dolorem sapiente.","Aut natus."],"status":"queued"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-campaign lfx-v2-campaign-service-briefs","description":"Get one campaign under a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"23c306fc-2672-44e3-a360-393f7d48f1dd","format":"uuid"},"example":"6e627787-92a4-43ee-8c98-6b97ec8af1e5"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"schema":{"type":"string","description":"Campaign UUID","example":"f41342df-fae6-4961-b9b7-6a3dde13126b","format":"uuid"},"example":"dd6598b8-8da5-472a-bcbb-2d8f068036bc"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Debitis quis quidem quam corrupti."},"example":"Voluptatem labore excepturi quibusdam iure."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign2"},"example":{"brief_id":"Ex voluptas aspernatur aperiam voluptates aliquam.","campaign_name":"Eaque ipsam harum nisi nobis et et.","id":"Explicabo est totam qui praesentium ipsam.","platform":"Ducimus neque.","platform_campaign_id":"Aut perferendis natus.","project_id":"Cum ipsum recusandae est adipisci ullam.","status":"Excepturi corporis ut.","version":9153915624091824945}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-campaign lfx-v2-campaign-service-briefs","description":"Replace a campaign (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"8a332b11-4d31-48bf-8c47-17384ebd6445","format":"uuid"},"example":"05ce6f1f-49a7-45ce-9c2c-c1a771a99cab"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"schema":{"type":"string","description":"Campaign UUID","example":"66025a18-b20e-4a9f-b7ee-db46d0219499","format":"uuid"},"example":"d0f0d2ab-d9dd-445b-bce5-1c29e2186349"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequestBody"},"example":{"campaign":{"campaign_name":"Ad voluptatem.","config":"Et expedita itaque quos.","status":"Tempore impedit."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Molestiae qui nihil."},"example":"Ut facere magni qui veniam architecto."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign3"},"example":{"brief_id":"Et aut consequatur inventore enim reiciendis.","campaign_name":"Ut mollitia accusantium dignissimos.","id":"Ullam omnis autem.","platform":"Neque ut rerum facilis quia molestiae ratione.","platform_campaign_id":"Fugit provident officia officiis.","project_id":"Id laboriosam nihil ea.","status":"Consequatur qui molestiae velit ratione nesciunt et.","version":476544424691753105}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Delectus voluptatem rerum et dolorum."},"example":"Quasi vel sint nisi laborum laboriosam assumenda."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection3"},"example":{"account_id":"Minima eaque earum.","has_credentials":true,"id":"Excepturi deleniti voluptas aperiam vel.","label":"Id temporibus et provident maiores neque quis.","login_customer_id":"Assumenda ipsa dolor.","project_id":"Laudantium amet veritatis rerum itaque consequatur laboriosam.","status":"active","version":8240960881778207137}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Porro eaque dolorem."},"example":"Sunt dolorum possimus eum."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection2"},"example":{"account_id":"Nobis ut asperiores quo aut.","has_credentials":true,"id":"Nihil quae porro.","label":"Nulla officia vitae perspiciatis eveniet.","login_customer_id":"Dicta voluptate aut in.","project_id":"Rem autem quae.","status":"error","version":7743336992980724195}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Et non et."},"example":"Delectus vel."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection4"},"example":{"account_id":"Quidem odio ab accusantium aut est deleniti.","has_credentials":true,"id":"Odio incidunt distinctio sint.","label":"Qui enim cumque saepe.","login_customer_id":"Praesentium voluptatibus deserunt aliquam necessitatibus eos.","project_id":"Aperiam quis quis reprehenderit non quae.","status":"active","version":5525678772225743935}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Eligendi et.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptatum explicabo vero repudiandae temporibus ut."},"example":"Est hic enim similique et eveniet vel."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection3"},"example":{"account_id":"In ipsa tempore incidunt velit provident perspiciatis.","brand_kit":"Aspernatur voluptatem nobis.","has_credentials":true,"id":"Nihil sunt doloremque.","label":"Ea architecto autem iste.","portal_id":"Omnis ut ex sunt.","project_id":"Ut ut odio odio sed minus quia.","sender_email":"Beatae ut modi voluptatum veniam.","sender_name":"Quibusdam consequuntur dolorem fugiat.","status":"deleted","version":5861862348723710898}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHubspotRequestBody"},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."},"credentials":{"private_app_token":"Molestiae suscipit non iusto."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ex quis molestias nam quia facilis."},"example":"Deserunt earum saepe repellat."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection2"},"example":{"account_id":"Porro ut et cupiditate.","brand_kit":"Animi molestiae aut fugit alias nisi qui.","has_credentials":true,"id":"Eius dicta.","label":"Non assumenda perspiciatis.","portal_id":"Voluptatem vel.","project_id":"Ullam distinctio dolore velit.","sender_email":"Dolore repudiandae necessitatibus dolorem.","sender_name":"Beatae quia minus vero at ut.","status":"active","version":2806381801577175312}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHubspotRequestBody"},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Blanditiis inventore similique est."},"example":"Possimus qui."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection4"},"example":{"account_id":"Debitis nam.","brand_kit":"Aut aut eos magnam quisquam et.","has_credentials":false,"id":"Eveniet sint voluptatibus quam dicta.","label":"Quis iste eaque.","portal_id":"Quia distinctio consequatur dolore.","project_id":"Consequuntur voluptatem est qui.","sender_email":"Corporis consequatur voluptatem voluptates.","sender_name":"Reprehenderit provident repudiandae sit.","status":"error","version":7483108258641908951}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialHubspotRequestBody"},"example":{"credentials":{"private_app_token":"Molestiae suscipit non iusto."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Sunt aliquam et velit fuga quos est.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aspernatur enim perferendis."},"example":"Soluta porro distinctio cupiditate."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection3"},"example":{"account_id":"Sed sed similique enim ea maiores.","has_credentials":false,"id":"In aperiam quam quis quam dolor.","label":"Sit cupiditate nobis iste qui rerum.","org_id":"Ratione expedita voluptatum commodi fuga ipsa quam.","project_id":"Earum iusto.","status":"active","version":4984404630797976872}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"},"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Molestias qui magnam impedit modi."},"example":"Ipsum ullam quis est incidunt debitis qui."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection2"},"example":{"account_id":"Libero ut est rerum est occaecati et.","has_credentials":false,"id":"Error nulla fuga fugit repellendus et accusamus.","label":"Nam quia aperiam sunt est.","org_id":"Consequuntur quod.","project_id":"Non sequi error assumenda quam deleniti et.","status":"inactive","version":5045833847874249009}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quia sequi consequatur aut dolorem."},"example":"Necessitatibus quam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection4"},"example":{"account_id":"Neque facilis est qui possimus eligendi.","has_credentials":false,"id":"Consequuntur qui quidem exercitationem.","label":"Sed numquam necessitatibus laudantium est ut.","org_id":"Et nobis voluptas dignissimos exercitationem illum.","project_id":"Ipsa quo vel architecto quas eligendi impedit.","status":"inactive","version":8080806383587730069}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialLinkedinAdsRequestBody"},"example":{"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Ex est facilis qui ducimus modi ut.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut maiores ea omnis quia quas."},"example":"Sunt odit molestiae ab praesentium enim."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection3"},"example":{"account_id":"Qui nobis et delectus quam.","app_id":"Est dolor ut corporis.","has_credentials":true,"id":"Laboriosam qui id enim.","label":"Nihil corrupti repellat eius.","page_id":"Aut fuga.","project_id":"Magni repudiandae.","status":"deleted","version":2782071096062379507}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."},"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Omnis ipsam ad sed laudantium ut ea."},"example":"Ad a et inventore tempora facilis dolores."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection2"},"example":{"account_id":"Ad inventore voluptatum eligendi quasi.","app_id":"Placeat unde distinctio ducimus qui.","has_credentials":true,"id":"Deleniti voluptates non sint inventore.","label":"Porro voluptate.","page_id":"Aperiam velit dolores.","project_id":"Iure ut nostrum facilis suscipit.","status":"inactive","version":358292288538552244}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Natus ratione."},"example":"Amet quam consequuntur nulla quaerat."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection4"},"example":{"account_id":"Quos hic doloribus.","app_id":"Qui a necessitatibus molestiae qui unde.","has_credentials":false,"id":"Illo vitae occaecati omnis.","label":"Explicabo possimus consequatur.","page_id":"Eos velit eum et sint corrupti.","project_id":"Pariatur debitis.","status":"deleted","version":3363918130362824521}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialMetaAdsRequestBody"},"example":{"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Dolorem qui explicabo consequuntur repellat non suscipit.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quidem est."},"example":"Corporis deserunt quae."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection3"},"example":{"account_id":"Dolor ut velit quam omnis et dolor.","customer_id":"Vitae vel omnis autem accusamus mollitia.","has_credentials":false,"id":"Maxime dicta qui aut.","label":"Velit et impedit facilis quaerat.","project_id":"Suscipit eos nam ipsa.","status":"deleted","version":443094643112211074}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."},"credentials":{"client_id":"Nisi blanditiis eveniet quasi ea.","client_secret":"Ut corporis dolorum consequatur eius perferendis nobis.","developer_token":"Et et itaque sunt.","refresh_token":"Vel voluptate necessitatibus."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Sint expedita fugit iste nemo voluptas."},"example":"Fugit unde ea quis."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection2"},"example":{"account_id":"Repellendus porro et distinctio.","customer_id":"Quia eaque.","has_credentials":true,"id":"Labore omnis provident nihil aut rerum est.","label":"Sint non.","project_id":"Amet rem ab.","status":"error","version":5425752825388180685}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut mollitia."},"example":"Pariatur ratione blanditiis."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection4"},"example":{"account_id":"Esse voluptate eum laboriosam distinctio non.","customer_id":"Id aut et adipisci quo ut.","has_credentials":false,"id":"Rem quia vel ipsam dolores.","label":"Ullam facilis ab sapiente voluptatem.","project_id":"Et qui ut libero numquam.","status":"inactive","version":8626906144120400240}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Nisi blanditiis eveniet quasi ea.","client_secret":"Ut corporis dolorum consequatur eius perferendis nobis.","developer_token":"Et et itaque sunt.","refresh_token":"Vel voluptate necessitatibus."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Voluptates ut culpa saepe necessitatibus.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Consequatur beatae."},"example":"Totam dolorem."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection3"},"example":{"account_id":"Iusto omnis.","has_credentials":false,"id":"Odio totam cum deleniti placeat fuga.","label":"Et ut quis ut.","project_id":"Saepe voluptatem reiciendis pariatur.","status":"error","version":757606501996618102}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."},"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Iure tempora non ea facilis nihil eius."},"example":"Corporis nesciunt ipsam voluptates magnam atque."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection2"},"example":{"account_id":"Non laudantium sunt minima repudiandae illo harum.","has_credentials":true,"id":"Atque laudantium.","label":"Enim accusantium labore esse numquam.","project_id":"Rerum earum rerum adipisci.","status":"inactive","version":6816002535409129912}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut enim."},"example":"Voluptas a natus est dolor voluptatum."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection4"},"example":{"account_id":"Et rerum in expedita.","has_credentials":true,"id":"Voluptatum ducimus tempore quae eum consequatur dolore.","label":"Quo veritatis quis delectus est.","project_id":"Illum illum dolores tenetur ut illum.","status":"active","version":4673192311933251173}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialRedditAdsRequestBody"},"example":{"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Assumenda nesciunt distinctio eaque.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Odio autem perferendis accusamus nulla placeat aut."},"example":"Aliquid quia natus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection3"},"example":{"account_id":"Id unde veritatis officiis aut fugiat.","funding_instrument_id":"Voluptas tenetur nobis facere.","has_credentials":true,"id":"Eaque voluptate reprehenderit sed voluptatem sed illo.","label":"Cum nisi numquam.","project_id":"Praesentium tenetur quas at ut necessitatibus dolor.","status":"active","version":4642810346647163563}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."},"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Sit dignissimos est."},"example":"Harum esse."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection2"},"example":{"account_id":"Vitae aut ea.","funding_instrument_id":"Aut sit architecto ut unde saepe.","has_credentials":true,"id":"Non iste ad rem quidem sunt qui.","label":"Deleniti doloribus debitis assumenda dignissimos.","project_id":"Ullam ab suscipit et labore harum.","status":"error","version":753723066786334950}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptatibus ut."},"example":"Fugit nostrum rerum."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection4"},"example":{"account_id":"Deserunt repellendus corrupti itaque quo sit.","funding_instrument_id":"Eum et ipsum expedita reprehenderit eos.","has_credentials":false,"id":"Blanditiis mollitia dolores illo nostrum magnam.","label":"Omnis nobis.","project_id":"Nostrum tempore.","status":"inactive","version":6605548766358472787}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialTwitterAdsRequestBody"},"example":{"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Ut architecto deleniti nostrum sed aperiam.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/jobs/{job_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-job lfx-v2-campaign-service-briefs","description":"Poll campaign-creation job status.","operationId":"lfx-v2-campaign-service-briefs#get-job","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"job_id","in":"path","description":"Job UUID","required":true,"schema":{"type":"string","description":"Job UUID","example":"79c02b27-dc40-4b89-9073-64a5596694c3","format":"uuid"},"example":"52112f6b-ba24-497d-8dd7-e7c129c8add5"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPollResponse"},"example":{"error":"Beatae est officia et sint.","job_id":"Et dignissimos ex.","result":[{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."},{"campaign_id":"Et voluptatem voluptatem voluptatem.","error":"Omnis doloribus qui.","ok":true,"platform":"Nobis velit in."}],"status":"running"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"Brief":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Vitae illo consequuntur esse."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Officia natus quia labore."},"event_details":{"description":"Extracted event/course details","example":"Quia recusandae dignissimos dolore harum totam."},"event_slug":{"type":"string","description":"Event/course slug","example":"Molestiae eius in reprehenderit nobis hic."},"id":{"type":"string","description":"Brief UUID","example":"Aliquid dignissimos minima aut fugit."},"keywords":{"description":"Keyword list","example":"Libero recusandae ut ullam aliquam."},"platforms":{"type":"array","items":{"type":"string","example":"Harum aliquam sunt eum."},"description":"Suggested default platforms","example":["Occaecati facere voluptatibus nihil nisi.","Distinctio tempore qui velit omnis dolorem est."]},"program_type":{"type":"string","description":"Funnel context","example":"Ipsum neque sapiente aliquid quidem velit."},"project_id":{"type":"string","description":"Owning project","example":"Et iure laudantium quia est molestiae."},"status":{"type":"string","description":"Lifecycle status","example":"draft","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Iure consequatur minus nostrum iste nemo."},"url":{"type":"string","description":"Event/course page URL","example":"Magni eos voluptatem sint hic ut."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3221003988771868872,"format":"int64"}},"example":{"copy":"Quod exercitationem.","etag":"Enim nam id.","event_details":"Repellendus eveniet veritatis impedit aut iste earum.","event_slug":"Sequi ea ut vero consequatur possimus.","id":"Ut dolores et incidunt.","keywords":"Pariatur saepe ab qui et.","platforms":["Et magni ad et provident.","Non quisquam iure.","Est ut aliquid."],"program_type":"Ipsam tempora est.","project_id":"Ipsa fugiat assumenda.","status":"archived","targeting":"Laboriosam sunt quis omnis animi.","url":"Assumenda sit delectus veniam.","version":3928799509074172783},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief2":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Sit facere iste et alias."},"event_details":{"description":"Extracted event/course details","example":"Ut iste et aspernatur delectus."},"event_slug":{"type":"string","description":"Event/course slug","example":"Aspernatur culpa sed non qui ad."},"id":{"type":"string","description":"Brief UUID","example":"Perspiciatis ea dolor."},"keywords":{"description":"Keyword list","example":"Magni ad doloremque soluta et quam totam."},"platforms":{"type":"array","items":{"type":"string","example":"Rerum voluptatem saepe eligendi eum non."},"description":"Suggested default platforms","example":["Totam occaecati enim eaque blanditiis deleniti reprehenderit.","Quas commodi.","Qui mollitia architecto recusandae voluptates excepturi reiciendis.","Consequatur amet dolorem qui."]},"program_type":{"type":"string","description":"Funnel context","example":"Ratione dicta."},"project_id":{"type":"string","description":"Owning project","example":"Dolores qui."},"status":{"type":"string","description":"Lifecycle status","example":"archived","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Eos repudiandae voluptate est enim aut laborum."},"url":{"type":"string","description":"Event/course page URL","example":"Voluptas qui."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7978828505057037537,"format":"int64"}},"example":{"copy":"Autem aperiam quis.","event_details":"Quae qui optio magni itaque iure.","event_slug":"Consequatur velit atque nam voluptatem in.","id":"Est et beatae.","keywords":"Enim odit numquam.","platforms":["Ea ut voluptatem velit.","Aut est est eum ut ut.","Et molestiae blanditiis nihil dolor exercitationem.","Placeat dolores culpa sed."],"program_type":"Repellendus autem ullam odio quis.","project_id":"Est officiis enim qui.","status":"draft","targeting":"Placeat aut dolores at.","url":"Explicabo voluptatem et.","version":2226888956028863556},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief3":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Quia amet vero dolorum incidunt officia nihil."},"event_details":{"description":"Extracted event/course details","example":"Optio enim nisi doloremque rerum sed consequatur."},"event_slug":{"type":"string","description":"Event/course slug","example":"Ut aspernatur et ad."},"id":{"type":"string","description":"Brief UUID","example":"Dignissimos voluptatem et labore sit atque qui."},"keywords":{"description":"Keyword list","example":"Dolores dignissimos perferendis tempore eum laboriosam iure."},"platforms":{"type":"array","items":{"type":"string","example":"Vel aut rerum voluptatem ad minus."},"description":"Suggested default platforms","example":["Cum nemo esse nobis.","Aut dolorem ea veniam.","Quae adipisci doloribus ullam.","Dolor eum et consequatur."]},"program_type":{"type":"string","description":"Funnel context","example":"Unde dolore id a necessitatibus fuga ducimus."},"project_id":{"type":"string","description":"Owning project","example":"Aut eaque eveniet autem."},"status":{"type":"string","description":"Lifecycle status","example":"draft","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"At magnam saepe incidunt nisi est."},"url":{"type":"string","description":"Event/course page URL","example":"Magnam sequi esse at."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3943386503686697829,"format":"int64"}},"example":{"copy":"Quos labore est ut ratione.","event_details":"Sint molestiae occaecati.","event_slug":"Deleniti non in iure dolores et quis.","id":"Distinctio possimus.","keywords":"Ut occaecati tenetur minima.","platforms":["Minus natus.","Doloribus nam eius nulla suscipit ullam accusamus."],"program_type":"Id ducimus excepturi est et est.","project_id":"Aliquid qui facilis neque sed alias.","status":"archived","targeting":"Sed impedit.","url":"Recusandae similique assumenda ut quia.","version":5475183181677687295},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief4":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Laborum quia beatae non et excepturi."},"event_details":{"description":"Extracted event/course details","example":"Molestias voluptatibus nam debitis hic qui."},"event_slug":{"type":"string","description":"Event/course slug","example":"Exercitationem voluptatum perspiciatis autem."},"id":{"type":"string","description":"Brief UUID","example":"Ut ut qui sed explicabo dolorem totam."},"keywords":{"description":"Keyword list","example":"Minus officiis et quisquam dicta rerum."},"platforms":{"type":"array","items":{"type":"string","example":"Sunt labore voluptate."},"description":"Suggested default platforms","example":["Ipsum quo possimus saepe quam.","Qui cum."]},"program_type":{"type":"string","description":"Funnel context","example":"Vero nulla laboriosam vel et et iure."},"project_id":{"type":"string","description":"Owning project","example":"Qui nesciunt sed magnam."},"status":{"type":"string","description":"Lifecycle status","example":"archived","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Enim est libero enim quo."},"url":{"type":"string","description":"Event/course page URL","example":"Eum ut."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":888507263129682759,"format":"int64"}},"example":{"copy":"Et fuga veniam.","event_details":"Aliquid eum itaque qui recusandae recusandae.","event_slug":"Illum expedita commodi in dolores nemo similique.","id":"Dolorem neque assumenda sed sapiente aspernatur ipsam.","keywords":"Ea et iusto doloremque odio pariatur.","platforms":["Voluptatem libero quia.","Harum molestiae cupiditate ab et eum ex.","Quia velit adipisci."],"program_type":"Dignissimos in tempore quo consequatur sint qui.","project_id":"Qui illo totam.","status":"archived","targeting":"Expedita animi ut velit at.","url":"Autem impedit.","version":6980228077781980481},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief5":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Quo soluta maxime temporibus earum et aperiam."},"event_details":{"description":"Extracted event/course details","example":"Amet officia occaecati quam nostrum quis voluptas."},"event_slug":{"type":"string","description":"Event/course slug","example":"Sapiente alias et error nobis minima."},"id":{"type":"string","description":"Brief UUID","example":"Ratione est omnis voluptas praesentium."},"keywords":{"description":"Keyword list","example":"Ab itaque tempore vel quia cum quas."},"platforms":{"type":"array","items":{"type":"string","example":"Fuga maxime tempora rem."},"description":"Suggested default platforms","example":["Debitis rerum.","Dolor pariatur."]},"program_type":{"type":"string","description":"Funnel context","example":"Laboriosam et."},"project_id":{"type":"string","description":"Owning project","example":"Aspernatur voluptatum facilis."},"status":{"type":"string","description":"Lifecycle status","example":"approved","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Minus sit necessitatibus."},"url":{"type":"string","description":"Event/course page URL","example":"Reprehenderit dolorem impedit qui suscipit ipsa ut."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4889810267187914144,"format":"int64"}},"example":{"copy":"Excepturi cumque ullam esse.","event_details":"Quia voluptates voluptates nisi nihil aut molestias.","event_slug":"Sed commodi voluptatibus.","id":"Et est debitis ea.","keywords":"Non autem voluptatem ut.","platforms":["Facilis asperiores enim nisi.","Perspiciatis optio.","Quia doloribus omnis et excepturi fugit dolores."],"program_type":"Nemo molestiae modi ut non atque.","project_id":"Eum voluptatum accusamus illo eveniet eum.","status":"approved","targeting":"Distinctio non recusandae et qui ut exercitationem.","url":"Nobis est fugit ratione consectetur exercitationem.","version":1659901820906198210},"required":["id","project_id","program_type","event_slug","status","version"]},"BriefInput":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Tempora voluptate fuga ea distinctio."},"event_details":{"description":"Extracted event/course details","example":"Pariatur mollitia incidunt autem."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Quis harum recusandae."},"keywords":{"description":"Keyword list","example":"Mollitia aut tempore rerum."},"platforms":{"type":"array","items":{"type":"string","example":"Repellat eum totam voluptatem eos doloribus."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Eligendi magni quidem sit in quia.","Perspiciatis alias et amet ex molestiae.","Minus libero et consequuntur suscipit."]},"program_type":{"type":"string","description":"Funnel context","example":"membership","enum":["events","education","membership"]},"targeting":{"description":"Targeting recommendation","example":"Eaque nesciunt."},"url":{"type":"string","description":"Event/course page URL","example":"Ipsum est amet quaerat."}},"example":{"copy":"Beatae consectetur aut omnis labore et quis.","event_details":"Natus deleniti est natus.","event_slug":"Inventore in eligendi ipsam.","keywords":"Dolores repellat.","platforms":["Itaque ut dolores in.","Expedita placeat dolores numquam molestias.","Aliquid deleniti fuga est aliquam."],"program_type":"education","targeting":"Similique omnis.","url":"Vel atque eos reiciendis sit minima non."},"required":["program_type","event_slug"]},"Campaign":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Voluptas necessitatibus cumque non porro qui molestias."},"campaign_name":{"type":"string","description":"Campaign name","example":"Nulla perferendis possimus deserunt iure et."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Omnis nesciunt qui."},"id":{"type":"string","description":"Campaign UUID","example":"Et minima dolorem sit."},"platform":{"type":"string","description":"Channel","example":"In praesentium cumque."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Pariatur odio molestias voluptatum."},"project_id":{"type":"string","description":"Owning project","example":"Tempora qui."},"status":{"type":"string","description":"Campaign status","example":"Ea quidem ducimus tempora ea molestias."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":971659538665007246,"format":"int64"}},"example":{"brief_id":"Odit provident sed quo aperiam.","campaign_name":"Voluptatem repellat expedita inventore.","etag":"Maxime hic aliquam ratione ipsum.","id":"Quia fuga atque.","platform":"Aspernatur nihil et incidunt dolorem dolor sed.","platform_campaign_id":"Delectus harum voluptates sit est corporis.","project_id":"Sequi culpa.","status":"Necessitatibus ut necessitatibus in.","version":5532967292288417247},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"Campaign2":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Et iste."},"campaign_name":{"type":"string","description":"Campaign name","example":"Rerum reiciendis similique."},"id":{"type":"string","description":"Campaign UUID","example":"Ea laboriosam totam unde."},"platform":{"type":"string","description":"Channel","example":"Voluptas ut nostrum officiis et accusamus provident."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Odio eos earum fuga non est."},"project_id":{"type":"string","description":"Owning project","example":"Ipsa sint optio nobis quae corporis."},"status":{"type":"string","description":"Campaign status","example":"Tempora qui incidunt tempora."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7916176946868712323,"format":"int64"}},"example":{"brief_id":"Voluptas incidunt eligendi consequatur.","campaign_name":"Earum temporibus ipsum necessitatibus ratione possimus eum.","id":"Voluptatem dolores tempora qui sit.","platform":"Labore eos et repudiandae odit omnis cupiditate.","platform_campaign_id":"Illo repellat et cum.","project_id":"Voluptates soluta animi sit ut.","status":"Repellat eveniet reiciendis consequatur consequatur sint.","version":2970088391723074573},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"Campaign3":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Et et sed."},"campaign_name":{"type":"string","description":"Campaign name","example":"Minima adipisci aut quia quisquam."},"id":{"type":"string","description":"Campaign UUID","example":"Molestiae quo non."},"platform":{"type":"string","description":"Channel","example":"Vel quaerat iure ipsam."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Necessitatibus reprehenderit illum."},"project_id":{"type":"string","description":"Owning project","example":"Reprehenderit natus."},"status":{"type":"string","description":"Campaign status","example":"Quia facilis sit."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7577275663228988931,"format":"int64"}},"example":{"brief_id":"Molestiae cupiditate quis modi blanditiis.","campaign_name":"Distinctio dignissimos.","id":"Nihil qui doloremque nam magnam aliquid impedit.","platform":"Tempora quod expedita magni.","platform_campaign_id":"Harum ipsa.","project_id":"Accusantium expedita alias placeat incidunt sit perspiciatis.","status":"Voluptatibus natus tempora commodi et.","version":6630207652424081561},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"CampaignCreateInput":{"type":"object","properties":{"config":{"description":"Per-platform campaign configuration","example":"Impedit ullam eaque suscipit harum sint neque."},"platforms":{"type":"array","items":{"type":"string","example":"twitter-ads","enum":["google-ads","linkedin-ads","meta-ads","reddit-ads","twitter-ads","microsoft-ads","hubspot"]},"description":"Platforms to create campaigns on (binding selection)","example":["meta-ads","google-ads","reddit-ads"],"minItems":1}},"example":{"config":"Optio consequatur nulla sint qui alias voluptatem.","platforms":["linkedin-ads","reddit-ads","google-ads"]},"required":["platforms"]},"CampaignUpdateInput":{"type":"object","properties":{"campaign_name":{"type":"string","description":"Campaign name","example":"Amet hic est."},"config":{"description":"Campaign configuration snapshot","example":"Quae esse mollitia et dolorum."},"status":{"type":"string","description":"Campaign status","example":"Quaerat occaecati rerum."}},"example":{"campaign_name":"Aspernatur quos est magnam voluptates.","config":"Dolores dolore id dolore sed.","status":"Libero autem quod saepe ullam ipsum."},"required":["campaign_name","status"]},"ConflictError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Accusantium totam quia."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":true}},"example":{"message":"Debitis adipisci.","ok":false},"required":["ok"]},"CreateBriefRequestBody":{"type":"object","properties":{"brief":{"$ref":"#/components/schemas/BriefInput"}},"example":{"brief":{"copy":"Blanditiis quidem qui iusto sed.","event_details":"Dolor molestias ipsa dolorum est.","event_slug":"Accusantium ad.","keywords":"Vel fuga recusandae.","platforms":["Iste autem veniam et enim unde.","Facere quam.","Rem quia consequuntur ut.","Consequatur quos atque voluptates quo sit."],"program_type":"membership","targeting":"Nulla ut laboriosam.","url":"Modi asperiores ut libero vel."}},"required":["brief"]},"CreateCampaignsRequestBody":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/CampaignCreateInput"}},"example":{"input":{"config":"Facere et exercitationem.","platforms":["microsoft-ads","twitter-ads","google-ads"]}},"required":["input"]},"CreateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}},"required":["config","credentials"]},"CreateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"},"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."},"credentials":{"private_app_token":"Molestiae suscipit non iusto."}},"required":["config","credentials"]},"CreateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"},"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}},"required":["config","credentials"]},"CreateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."},"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}},"required":["config","credentials"]},"CreateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."},"credentials":{"client_id":"Nisi blanditiis eveniet quasi ea.","client_secret":"Ut corporis dolorum consequatur eius perferendis nobis.","developer_token":"Et et itaque sunt.","refresh_token":"Vel voluptate necessitatibus."}},"required":["config","credentials"]},"CreateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."},"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}},"required":["config","credentials"]},"CreateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."},"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}},"required":["config","credentials"]},"GoogleAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptatibus aliquam et."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Mollitia dolorem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Modi consequatur molestias et et deleniti."},"label":{"type":"string","description":"Optional friendly name","example":"Atque hic quam impedit placeat."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Consequatur enim voluptas numquam aut et."},"project_id":{"type":"string","description":"Owning project","example":"Reiciendis odit eos et quidem."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2538353845791645445,"format":"int64"}},"example":{"account_id":"Rem fugit accusantium est.","etag":"Dolorem asperiores autem aut aut soluta.","has_credentials":true,"id":"Eius dolor magnam necessitatibus amet.","label":"Rerum tempore tempora.","login_customer_id":"Et est et corporis.","project_id":"Earum aut qui.","status":"active","version":6983147448769937945},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"GoogleAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Sed ipsam enim esse iure est."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Minima dolorem omnis aspernatur veritatis adipisci."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptas ipsam iste."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Quis quod aut sunt ex et autem."},"project_id":{"type":"string","description":"Owning project","example":"Repellendus adipisci sit et enim quasi ut."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5457180552368415477,"format":"int64"}},"example":{"account_id":"Natus aliquam aut quas aliquid.","has_credentials":true,"id":"Non adipisci omnis veritatis omnis.","label":"Placeat et.","login_customer_id":"Laboriosam praesentium beatae quis.","project_id":"Omnis reprehenderit.","status":"active","version":5939335900418206229},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vero provident voluptas consequatur iusto."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Eius harum."},"label":{"type":"string","description":"Optional friendly name","example":"Nihil excepturi provident saepe aliquam architecto."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Ut magnam aut illum commodi placeat sed."},"project_id":{"type":"string","description":"Owning project","example":"Qui est nemo ullam."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8077434258112236542,"format":"int64"}},"example":{"account_id":"Voluptas quasi esse corporis.","has_credentials":false,"id":"Minus pariatur praesentium amet.","label":"Sint velit earum laboriosam dignissimos reiciendis et.","login_customer_id":"Deserunt reiciendis dolorem totam voluptatem.","project_id":"Libero pariatur ut.","status":"active","version":6561992588765026566},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Laudantium doloremque aut eligendi perferendis aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Amet qui provident."},"label":{"type":"string","description":"Optional friendly name","example":"Ut ea."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Ea consectetur delectus."},"project_id":{"type":"string","description":"Owning project","example":"Repudiandae molestias est consectetur porro quo pariatur."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":10157424754048271,"format":"int64"}},"example":{"account_id":"Aut earum rerum ducimus dicta velit.","has_credentials":true,"id":"Id officiis architecto.","label":"Sit iste ea.","login_customer_id":"Molestiae dolorum non perspiciatis a.","project_id":"Et libero ea aut omnis.","status":"error","version":8687544223346888800},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Sint autem repudiandae asperiores et."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Quia consectetur non nulla dolore quibusdam ducimus."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Soluta quod."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Iste explicabo minima commodi qui quia est."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Omnis inventore voluptatum ab.","client_secret":"Laboriosam quos omnis deserunt libero consequatur quisquam.","developer_token":"Sapiente ex est modi cupiditate possimus ducimus.","refresh_token":"At fugiat."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Sint suscipit."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Quo veritatis nisi velit distinctio animi."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Non neque optio."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Dolore voluptatem qui eveniet amet."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptate et."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Architecto molestiae cum."},"project_id":{"type":"string","description":"Owning project","example":"Qui pariatur odio reprehenderit nam."},"sender_email":{"type":"string","description":"Default sender address","example":"Perspiciatis consequatur beatae sunt officiis sequi qui."},"sender_name":{"type":"string","description":"Default sender name","example":"Sapiente perspiciatis dolores dignissimos soluta non vel."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6078188127403523196,"format":"int64"}},"example":{"account_id":"Dolor molestiae quia voluptatem deserunt libero.","brand_kit":"Molestiae soluta vel ad et in voluptatem.","etag":"Aut impedit a veritatis.","has_credentials":false,"id":"Provident rerum et voluptas tenetur esse.","label":"Quaerat ipsa voluptatem.","portal_id":"Consequuntur sed ea sunt debitis.","project_id":"Assumenda sed minima nihil.","sender_email":"Voluptas qui voluptas veniam sint voluptas.","sender_name":"Non illo ab in voluptatem minus est.","status":"active","version":1552956989772584247},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"HubspotConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Eius quo."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Dolorem impedit sint consequatur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Iusto in commodi velit in odio."},"label":{"type":"string","description":"Optional friendly name","example":"Non architecto."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Et corrupti magnam deleniti nam laboriosam esse."},"project_id":{"type":"string","description":"Owning project","example":"Aliquam voluptatem sunt et eveniet."},"sender_email":{"type":"string","description":"Default sender address","example":"Eaque ut voluptatibus qui qui officia."},"sender_name":{"type":"string","description":"Default sender name","example":"Alias quis voluptates nihil odio facere magnam."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7096858184495555885,"format":"int64"}},"example":{"account_id":"Officia suscipit qui molestias aliquid dolorum consequatur.","brand_kit":"Hic ea et.","has_credentials":false,"id":"Nihil labore accusamus illum in delectus magnam.","label":"Porro voluptatum nemo adipisci.","portal_id":"Enim aut.","project_id":"Velit sed laboriosam placeat nesciunt.","sender_email":"Dolores sed.","sender_name":"Optio enim repellendus commodi quia quod sit.","status":"inactive","version":3587423604677944667},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Eligendi corporis tenetur delectus excepturi et."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Ut est est mollitia."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aperiam impedit omnis explicabo."},"label":{"type":"string","description":"Optional friendly name","example":"Enim temporibus facere."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Id libero sunt."},"project_id":{"type":"string","description":"Owning project","example":"Et et commodi autem."},"sender_email":{"type":"string","description":"Default sender address","example":"Suscipit consequatur iure dolor sit quis ut."},"sender_name":{"type":"string","description":"Default sender name","example":"Natus enim non."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2722095064844656519,"format":"int64"}},"example":{"account_id":"Doloribus similique et.","brand_kit":"Voluptas earum provident qui.","has_credentials":true,"id":"Corporis omnis.","label":"Delectus nisi minima quas voluptatem consequuntur.","portal_id":"Aliquam dolorem voluptas autem enim aut ut.","project_id":"Ea in sunt odit aspernatur.","sender_email":"Eum ut rem illum.","sender_name":"Autem ratione est exercitationem sapiente a animi.","status":"error","version":6167429858997341484},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quam aliquid reiciendis maiores similique ab."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Iure consectetur libero."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Facere dolorum rem laborum dolor provident."},"label":{"type":"string","description":"Optional friendly name","example":"Vitae voluptas fuga aut minima inventore tenetur."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Accusamus optio consequatur excepturi rerum."},"project_id":{"type":"string","description":"Owning project","example":"Odio aut suscipit."},"sender_email":{"type":"string","description":"Default sender address","example":"In officiis dolores delectus architecto."},"sender_name":{"type":"string","description":"Default sender name","example":"Velit eligendi omnis est vel est et."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7137337173165660443,"format":"int64"}},"example":{"account_id":"Ratione architecto corporis totam et.","brand_kit":"Tempora reprehenderit eveniet.","has_credentials":true,"id":"Accusamus aut deleniti libero veritatis cum.","label":"Quidem perferendis.","portal_id":"Tempora magnam ea repellat qui.","project_id":"Deleniti dolorem voluptas harum soluta repudiandae quisquam.","sender_email":"Eveniet dolor necessitatibus est mollitia.","sender_name":"Officiis dolorem necessitatibus blanditiis rerum.","status":"active","version":6334382601164890326},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Totam et voluptates atque consequatur consequuntur voluptates."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Adipisci minus et vero."},"label":{"type":"string","description":"Optional friendly name","example":"Commodi facere vel."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Quos provident."},"sender_email":{"type":"string","description":"Default sender address","example":"Et fugiat reprehenderit officiis modi quae consequatur."},"sender_name":{"type":"string","description":"Default sender name","example":"Et culpa culpa veritatis."}},"example":{"account_id":"Ea et quidem quibusdam officiis dolor.","brand_kit":"Aut mollitia pariatur perferendis placeat dolores.","label":"Ea perspiciatis rerum occaecati incidunt deleniti sunt.","portal_id":"Nisi sunt.","sender_email":"Sint dolor eaque culpa.","sender_name":"Eius omnis id itaque."},"required":["account_id"]},"HubspotCredentials":{"type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Molestiae veniam exercitationem blanditiis ratione non."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Sed dolorum cupiditate quia qui dicta."},"required":["private_app_token"]},"InternalServerError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"JobCreateResponse":{"type":"object","properties":{"job_id":{"type":"string","description":"Poll GET /projects/{projectId}/jobs/{jobId}","example":"Consequatur ut."},"platforms":{"type":"array","items":{"type":"string","example":"Commodi qui mollitia et eum veniam."},"description":"Platforms this job will create on","example":["Eaque voluptatem omnis architecto cupiditate.","Sint culpa sed minima."]},"status":{"type":"string","description":"Initial status (always 'queued' on create)","example":"queued","enum":["queued"]}},"example":{"job_id":"Quos enim.","platforms":["Molestiae ad voluptatum ducimus accusantium et.","Quaerat et eius culpa magni illum consequatur.","Soluta dolore vitae."],"status":"queued"},"required":["job_id","status","platforms"]},"JobPollResponse":{"type":"object","properties":{"error":{"type":"string","description":"Terminal error, if any","example":"Sequi aut veniam ratione."},"job_id":{"type":"string","description":"Job UUID","example":"Quia magnam culpa ipsam totam nulla."},"result":{"type":"array","items":{"$ref":"#/components/schemas/PlatformResult"},"description":"Per-platform results, written once when the job reaches a terminal state","example":[{"campaign_id":"Nemo perspiciatis et qui aliquam doloremque itaque.","error":"Magni autem.","ok":false,"platform":"Ut totam ut."},{"campaign_id":"Nemo perspiciatis et qui aliquam doloremque itaque.","error":"Magni autem.","ok":false,"platform":"Ut totam ut."},{"campaign_id":"Nemo perspiciatis et qui aliquam doloremque itaque.","error":"Magni autem.","ok":false,"platform":"Ut totam ut."}]},"status":{"type":"string","description":"Job status","example":"running","enum":["queued","running","succeeded","partial","failed"]}},"example":{"error":"Mollitia quidem quo et enim.","job_id":"Perferendis omnis ad voluptas esse.","result":[{"campaign_id":"Nemo perspiciatis et qui aliquam doloremque itaque.","error":"Magni autem.","ok":false,"platform":"Ut totam ut."},{"campaign_id":"Nemo perspiciatis et qui aliquam doloremque itaque.","error":"Magni autem.","ok":false,"platform":"Ut totam ut."}],"status":"failed"},"required":["job_id","status"]},"LinkedinAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nobis qui sunt laboriosam ipsa."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut inventore consequatur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Rerum rem."},"label":{"type":"string","description":"Optional friendly name","example":"In vel."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Autem eaque eaque dicta ut magni."},"project_id":{"type":"string","description":"Owning project","example":"Vitae qui explicabo tempora accusantium aut."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3265309804259735983,"format":"int64"}},"example":{"account_id":"Alias quod minima blanditiis.","etag":"Iure dolores dolores dolorem sit nihil.","has_credentials":true,"id":"Error deleniti vitae molestiae qui nobis.","label":"Quia enim non asperiores velit.","org_id":"Sit est atque qui quaerat qui vitae.","project_id":"Aut temporibus dolorem cumque quis.","status":"inactive","version":4548596825330631938},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"LinkedinAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Et laboriosam veritatis saepe numquam."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quas delectus a maxime consequatur iste eum."},"label":{"type":"string","description":"Optional friendly name","example":"Et at iusto optio."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Qui quisquam quae."},"project_id":{"type":"string","description":"Owning project","example":"Vel impedit et unde et rerum."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1129434020019069718,"format":"int64"}},"example":{"account_id":"Ea nostrum corrupti ipsum minima consequuntur.","has_credentials":true,"id":"Magni temporibus et molestias.","label":"Iste ullam sunt rem non doloribus omnis.","org_id":"Hic et illo aut natus et accusamus.","project_id":"Molestias et officiis asperiores quibusdam accusamus.","status":"active","version":8161899588231294433},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Eos quidem debitis iusto molestias itaque consequatur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Qui et dolorem."},"label":{"type":"string","description":"Optional friendly name","example":"Expedita error cumque."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Fugit et soluta."},"project_id":{"type":"string","description":"Owning project","example":"Laboriosam repellat harum expedita aut quidem beatae."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7037294126068689767,"format":"int64"}},"example":{"account_id":"Iure ut.","has_credentials":true,"id":"Et qui officiis repudiandae qui iste accusantium.","label":"Consequatur provident ea sint.","org_id":"Eum quibusdam et minus id deleniti placeat.","project_id":"Voluptatem iste nemo numquam unde perspiciatis.","status":"active","version":3173101195127892589},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Autem quia velit aliquid quae."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quia et non id sunt adipisci consectetur."},"label":{"type":"string","description":"Optional friendly name","example":"Soluta nostrum cumque explicabo pariatur voluptatum molestias."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Consequatur vero possimus."},"project_id":{"type":"string","description":"Owning project","example":"Modi quo et inventore eum autem quia."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":700201239225519995,"format":"int64"}},"example":{"account_id":"Sequi dolor eos fugiat rem.","has_credentials":false,"id":"Maiores voluptas.","label":"Qui facilis possimus.","org_id":"Pariatur et et deserunt facere ea incidunt.","project_id":"Corporis quam quos dolor id debitis eos.","status":"inactive","version":1986183550074348775},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Officia dolores et reprehenderit."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Ex dolor.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Voluptate est reprehenderit consectetur accusamus eaque inventore."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Aut explicabo."},"required":["access_token"]},"MetaAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Libero officia et quia."},"app_id":{"type":"string","description":"Meta app ID","example":"Necessitatibus eos rerum."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Qui maxime eos deserunt."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Est consequatur rerum neque velit nulla modi."},"label":{"type":"string","description":"Optional friendly name","example":"Nihil at."},"page_id":{"type":"string","description":"Facebook page ID","example":"Expedita sed velit distinctio."},"project_id":{"type":"string","description":"Owning project","example":"Optio quia similique vero est impedit quo."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4061552416134608654,"format":"int64"}},"example":{"account_id":"Vero aut repellendus reiciendis.","app_id":"Ea ad ducimus consequatur aut.","etag":"Atque et.","has_credentials":true,"id":"Veniam amet et quisquam voluptatem fuga.","label":"Optio rerum vel molestiae id.","page_id":"Quia iure aut tenetur.","project_id":"Qui quas aut provident suscipit.","status":"inactive","version":8332327938373479794},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MetaAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Non impedit incidunt."},"app_id":{"type":"string","description":"Meta app ID","example":"Alias laudantium sit et."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Molestiae modi."},"label":{"type":"string","description":"Optional friendly name","example":"Temporibus ullam sint aliquam ea."},"page_id":{"type":"string","description":"Facebook page ID","example":"Rerum corrupti."},"project_id":{"type":"string","description":"Owning project","example":"Libero consectetur non."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1431900350448492398,"format":"int64"}},"example":{"account_id":"Amet et vel ipsa ad doloremque qui.","app_id":"Eum voluptatem debitis aliquid accusamus eligendi itaque.","has_credentials":false,"id":"Sed eius reiciendis rerum voluptatem suscipit rerum.","label":"Voluptas voluptatem eum consequuntur.","page_id":"Est quidem in.","project_id":"Et ex aut cupiditate qui placeat omnis.","status":"inactive","version":2895836647375905460},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nam rerum omnis praesentium omnis amet quia."},"app_id":{"type":"string","description":"Meta app ID","example":"At explicabo reiciendis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Est temporibus explicabo doloremque possimus."},"label":{"type":"string","description":"Optional friendly name","example":"Autem repellat et."},"page_id":{"type":"string","description":"Facebook page ID","example":"Officia rerum eligendi ex."},"project_id":{"type":"string","description":"Owning project","example":"Cupiditate ab omnis nesciunt consequatur commodi."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5777051591540907893,"format":"int64"}},"example":{"account_id":"Voluptates voluptatem similique aut.","app_id":"Natus expedita est sed.","has_credentials":true,"id":"At ratione unde dicta.","label":"Voluptas perferendis et perferendis.","page_id":"Voluptatem sit excepturi eaque.","project_id":"Doloribus aspernatur quod ipsum pariatur explicabo nesciunt.","status":"error","version":4610591738843411818},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Repudiandae hic nostrum qui sed est architecto."},"app_id":{"type":"string","description":"Meta app ID","example":"Eveniet magnam dolor sint."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ducimus qui quas neque eum voluptatem."},"label":{"type":"string","description":"Optional friendly name","example":"Consectetur consequatur autem eveniet magnam dolor."},"page_id":{"type":"string","description":"Facebook page ID","example":"Id doloremque ad dolores tempore ut est."},"project_id":{"type":"string","description":"Owning project","example":"Velit corporis nemo."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3642485435905489461,"format":"int64"}},"example":{"account_id":"Doloremque pariatur qui magni facere.","app_id":"Ut voluptas est est accusamus.","has_credentials":false,"id":"Ipsa nam ipsa magnam praesentium eos cum.","label":"Illum natus doloremque facere quis et beatae.","page_id":"Sit animi dolor ex saepe ipsa.","project_id":"Sed est recusandae enim quidem.","status":"active","version":1460268576636670658},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Adipisci ut."},"label":{"type":"string","description":"Optional friendly name","example":"Excepturi natus est est saepe doloribus unde."},"page_id":{"type":"string","description":"Facebook page ID","example":"Aut perspiciatis."}},"example":{"account_id":"act_193556282970417","app_id":"Dolores earum.","label":"Sit totam ut et.","page_id":"Quibusdam ea nam aut."},"required":["account_id"]},"MetaAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Optio non."},"app_secret":{"type":"string","description":"Meta app secret","example":"Optio in ratione accusamus quasi."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Ipsam eligendi est dolore.","app_secret":"Consequatur aut vero."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ut itaque voluptatem unde adipisci aut."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Non laudantium quis ab non sunt."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut praesentium eos ex laborum et doloremque."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Et natus possimus cupiditate."},"label":{"type":"string","description":"Optional friendly name","example":"Repudiandae eum."},"project_id":{"type":"string","description":"Owning project","example":"Molestiae quia neque atque delectus."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":9192676630235860462,"format":"int64"}},"example":{"account_id":"Voluptatem velit odio temporibus aut.","customer_id":"Enim velit voluptates officiis tempora possimus rerum.","etag":"Vitae dolor soluta beatae.","has_credentials":true,"id":"Quo est.","label":"Quas quia quam voluptate nam iste.","project_id":"Beatae totam.","status":"error","version":1007882038292754957},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MicrosoftAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quisquam sit ut aliquid consectetur."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Quia assumenda aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Nobis ipsam blanditiis commodi accusamus maxime."},"label":{"type":"string","description":"Optional friendly name","example":"Iusto aspernatur fugit et facere eligendi."},"project_id":{"type":"string","description":"Owning project","example":"Quia sed eum ad sunt impedit sint."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8275500991568401541,"format":"int64"}},"example":{"account_id":"Id voluptatem et et et eveniet.","customer_id":"Ex dolor quia et dolores.","has_credentials":true,"id":"Voluptate quam delectus optio.","label":"Molestiae fuga.","project_id":"Ducimus aliquam vel cum alias.","status":"error","version":2847235492838917454},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ipsum est a."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Molestiae similique optio et corporis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Distinctio ea."},"label":{"type":"string","description":"Optional friendly name","example":"Velit architecto error consequatur nam."},"project_id":{"type":"string","description":"Owning project","example":"Ab iste earum consequatur laboriosam maxime nemo."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6583480909021941947,"format":"int64"}},"example":{"account_id":"Ex enim rem.","customer_id":"Quo sit iure quos aut.","has_credentials":false,"id":"Eligendi voluptatem repellat consectetur alias reiciendis voluptas.","label":"Dolorum enim doloribus.","project_id":"Cumque est quis nostrum quod.","status":"active","version":2799898264679301316},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Repellendus et neque deserunt eligendi rerum architecto."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Sapiente dolor unde harum dolorem iusto quia."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Facere debitis quo."},"label":{"type":"string","description":"Optional friendly name","example":"Modi eveniet."},"project_id":{"type":"string","description":"Owning project","example":"Fuga quaerat sed."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":402075575675880099,"format":"int64"}},"example":{"account_id":"Ratione ut laboriosam dolorem ut dolorem odio.","customer_id":"Inventore iste nemo rerum corporis repellat nemo.","has_credentials":true,"id":"Eos doloremque qui repellendus.","label":"Ea et nesciunt et culpa similique.","project_id":"Incidunt in.","status":"deleted","version":2480470963761289215},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Rerum maiores in alias est laboriosam saepe."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Perferendis placeat earum occaecati et."},"label":{"type":"string","description":"Optional friendly name","example":"Unde perspiciatis."}},"example":{"account_id":"Assumenda quam possimus aut ipsa.","customer_id":"Quae et perspiciatis numquam.","label":"Eaque magni omnis dolorem."},"required":["account_id"]},"MicrosoftAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Accusantium est."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Molestias reprehenderit."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Dolorem similique sapiente."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Pariatur esse."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Ea pariatur.","client_secret":"Aut rem molestiae assumenda ea.","developer_token":"Sed qui dolor illo facere ut voluptatem.","refresh_token":"Nam repellat."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PlatformResult":{"type":"object","properties":{"campaign_id":{"type":"string","description":"Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost.","example":"Laudantium dolor ipsam recusandae maxime."},"error":{"type":"string","description":"Failure reason (present when not ok)","example":"Repellendus assumenda voluptatem aut debitis."},"ok":{"type":"boolean","description":"Whether the campaign was created (or reused) successfully","example":false},"platform":{"type":"string","description":"Platform this result is for","example":"In commodi et asperiores et."}},"example":{"campaign_id":"Et consequatur et et itaque qui repudiandae.","error":"Atque ullam qui rerum numquam quo omnis.","ok":true,"platform":"Unde velit eos eius accusantium odit rerum."},"required":["platform","ok"]},"PreconditionFailedError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ut quas officiis mollitia ducimus sunt repellendus."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Aliquam est autem vel eos minima."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Odit illo ut ut."},"label":{"type":"string","description":"Optional friendly name","example":"Debitis atque laboriosam unde facilis."},"project_id":{"type":"string","description":"Owning project","example":"Nam modi nostrum assumenda alias sed."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2463177480955435124,"format":"int64"}},"example":{"account_id":"Facilis dolor consectetur sed.","etag":"Et itaque.","has_credentials":false,"id":"Quas est deserunt maiores consequatur eum.","label":"Occaecati hic ut magnam facilis.","project_id":"Voluptatum pariatur non ratione quo ullam.","status":"inactive","version":3072744263422347458},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"RedditAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Est quia ducimus inventore."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Nisi consequatur non quam suscipit ea ea."},"label":{"type":"string","description":"Optional friendly name","example":"Autem aut maiores ut sint."},"project_id":{"type":"string","description":"Owning project","example":"Quia sed harum."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4610200042662121887,"format":"int64"}},"example":{"account_id":"Impedit perspiciatis ipsam sunt distinctio consequuntur.","has_credentials":true,"id":"Et mollitia exercitationem cumque.","label":"Eaque omnis nihil itaque.","project_id":"Similique sequi occaecati.","status":"error","version":2750059486165092858},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Deserunt provident voluptates cumque reiciendis error minus."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Recusandae amet."},"label":{"type":"string","description":"Optional friendly name","example":"Expedita nihil molestiae soluta consequuntur sapiente."},"project_id":{"type":"string","description":"Owning project","example":"Magni ut amet."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8154718885862958757,"format":"int64"}},"example":{"account_id":"Rerum nesciunt veritatis consequatur porro.","has_credentials":false,"id":"Qui voluptas culpa.","label":"Delectus non aut ipsum.","project_id":"Iste nihil itaque et.","status":"deleted","version":1595767401527646160},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nemo aut et qui."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ratione provident laborum."},"label":{"type":"string","description":"Optional friendly name","example":"Et quisquam dolores."},"project_id":{"type":"string","description":"Owning project","example":"Sed voluptatem repudiandae soluta."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8907067316289641877,"format":"int64"}},"example":{"account_id":"Aliquid porro deserunt nostrum sed sint voluptas.","has_credentials":true,"id":"Officiis et error nemo iste harum.","label":"Culpa est ex.","project_id":"Commodi occaecati.","status":"error","version":2407825525433943238},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Ipsam aperiam et totam in."}},"example":{"account_id":"t2_gv9wtbfa","label":"Numquam voluptatem maiores ipsam dicta cum odit."},"required":["account_id"]},"RedditAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Cumque laborum ipsum accusantium voluptatem libero."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Eligendi non."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Necessitatibus non eum ipsum eveniet quam."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Aut amet aut voluptatum magnam.","client_secret":"Accusamus eos neque velit.","refresh_token":"In iure molestiae quisquam."},"required":["client_id","client_secret","refresh_token"]},"ServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"SetCredentialGoogleAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Natus provident est.","client_secret":"Id dolore.","developer_token":"Non error.","refresh_token":"Accusamus aperiam eos et architecto voluptas rerum."}},"required":["credentials"]},"SetCredentialHubspotRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Molestiae suscipit non iusto."}},"required":["credentials"]},"SetCredentialLinkedinAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Eum est molestiae necessitatibus magni."}},"required":["credentials"]},"SetCredentialMetaAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Eius est rerum illo mollitia.","app_secret":"Et dolor et consectetur sint error."}},"required":["credentials"]},"SetCredentialRedditAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Magnam ut similique ea minima magnam.","client_secret":"Ut quo iure.","refresh_token":"Sit est dolores est et."}},"required":["credentials"]},"SetCredentialTwitterAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Possimus et.","access_token_secret":"Deserunt consequuntur omnis.","consumer_key":"Dignissimos voluptas magni consequatur dignissimos itaque.","consumer_secret":"Ut aperiam."}},"required":["credentials"]},"TwitterAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Temporibus in qui provident corrupti ut similique."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Consectetur voluptas voluptas voluptatem."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Quia facere vitae aperiam."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Laudantium eligendi ut et."},"label":{"type":"string","description":"Optional friendly name","example":"Saepe beatae."},"project_id":{"type":"string","description":"Owning project","example":"Maiores nam sequi iste iusto reiciendis ipsa."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4247878238148524808,"format":"int64"}},"example":{"account_id":"Aut et ut dolorem tempore.","etag":"Commodi sit omnis.","funding_instrument_id":"Porro saepe eius earum quo porro.","has_credentials":true,"id":"Corrupti dicta pariatur totam quas deserunt.","label":"In voluptate debitis earum voluptates repellat.","project_id":"Quis impedit.","status":"error","version":1517802020058879691},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"TwitterAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Est aut et rem eos quia et."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Facere dolorum ab laboriosam placeat atque cumque."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quam fugiat adipisci non rerum et."},"label":{"type":"string","description":"Optional friendly name","example":"Quaerat laborum voluptatibus accusamus."},"project_id":{"type":"string","description":"Owning project","example":"Et eum tenetur."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":942544011211036746,"format":"int64"}},"example":{"account_id":"Eaque eos veniam quidem maiores adipisci.","funding_instrument_id":"Molestias ullam aut odit repellat nihil at.","has_credentials":true,"id":"Alias nihil.","label":"Voluptas ullam optio.","project_id":"Temporibus error incidunt.","status":"deleted","version":5983859909197428373},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Aliquam perspiciatis libero est ratione pariatur."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Corrupti quam unde nemo ipsum et illum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Reprehenderit nemo voluptatibus."},"label":{"type":"string","description":"Optional friendly name","example":"Ipsa quis animi aliquam sunt."},"project_id":{"type":"string","description":"Owning project","example":"Perferendis corrupti mollitia tempore aut."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3919079788188515327,"format":"int64"}},"example":{"account_id":"Repellendus voluptatem.","funding_instrument_id":"Saepe quo quis ut in voluptatem.","has_credentials":true,"id":"Rerum est.","label":"Minus aut ut non.","project_id":"Enim est sapiente.","status":"error","version":6704106453019920248},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Qui doloremque magni est error maiores."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Est eum nemo architecto est aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Nesciunt magnam omnis est unde."},"label":{"type":"string","description":"Optional friendly name","example":"Eum animi sed explicabo et quo et."},"project_id":{"type":"string","description":"Owning project","example":"Rerum eius vitae perferendis quod."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7817365326411446744,"format":"int64"}},"example":{"account_id":"Ut et sit quae similique inventore.","funding_instrument_id":"Illum laboriosam debitis aut vitae est est.","has_credentials":false,"id":"Ab culpa consequatur.","label":"Excepturi et eveniet sunt ut.","project_id":"Veniam omnis.","status":"active","version":328362383505116272},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Dolor et aliquam deleniti."},"label":{"type":"string","description":"Optional friendly name","example":"Et nemo dicta illum omnis."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Est dolor velit et voluptatem mollitia placeat.","label":"Nemo cum delectus laudantium."},"required":["account_id"]},"TwitterAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Perferendis et."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Beatae facilis dolor quod nihil quia."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"A debitis ut ea."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Quos qui quo et."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Non consequuntur qui qui impedit assumenda voluptas.","access_token_secret":"Consequuntur provident nostrum vel ut.","consumer_key":"Sed voluptas fuga officia nulla.","consumer_secret":"Recusandae consequatur nulla."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]},"UpdateCampaignRequestBody":{"type":"object","properties":{"campaign":{"$ref":"#/components/schemas/CampaignUpdateInput"}},"example":{"campaign":{"campaign_name":"Ad voluptatem.","config":"Et expedita itaque quos.","status":"Tempore impedit."}},"required":["campaign"]},"UpdateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"UpdateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Voluptas aperiam.","brand_kit":"Itaque fugit rerum cumque.","label":"Dolores enim.","portal_id":"Assumenda harum eos eveniet ut et laboriosam.","sender_email":"Quos expedita voluptas qui eaque.","sender_name":"Laudantium nostrum ratione id eius laborum aspernatur."}},"required":["config"]},"UpdateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Dolorum consequatur in est.","org_id":"208777"}},"required":["config"]},"UpdateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Distinctio sunt aliquid harum quis nam rerum.","label":"Excepturi blanditiis officiis quia quisquam.","page_id":"Distinctio voluptas eius error autem est."}},"required":["config"]},"UpdateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Enim non rem error voluptas deserunt.","customer_id":"Officiis aperiam quos.","label":"Nihil dolores perspiciatis porro veniam distinctio."}},"required":["config"]},"UpdateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Sit tempore."}},"required":["config"]},"UpdateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Deserunt in hic dolorum facere ipsa et.","label":"Repellat veniam."}},"required":["config"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"JWT issued by Heimdall; audience is this service.","scheme":"bearer"}}},"tags":[{"name":"lfx-v2-campaign-service-svc","description":"The campaign service supports Marketing Operations campaign creation and management."},{"name":"lfx-v2-campaign-service-briefs","description":"Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation."},{"name":"lfx-v2-campaign-service-connections","description":"Manage a project's singleton, per-provider ad-platform connections."}]} \ No newline at end of file diff --git a/cmd/campaign-service/kodata/gen/http/openapi3.yaml b/cmd/campaign-service/kodata/gen/http/openapi3.yaml index e05fa2db..609c0b00 100644 --- a/cmd/campaign-service/kodata/gen/http/openapi3.yaml +++ b/cmd/campaign-service/kodata/gen/http/openapi3.yaml @@ -7,6 +7,939 @@ servers: - url: http://localhost:80 description: Default server for lfx-v2-campaign-service paths: + /projects/{project_id}/briefs: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-brief lfx-v2-campaign-service-briefs + description: Create a brief. + operationId: lfx-v2-campaign-service-briefs#create-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBriefRequestBody' + example: + brief: + copy: Blanditiis quidem qui iusto sed. + event_details: Dolor molestias ipsa dolorum est. + event_slug: Accusantium ad. + keywords: Vel fuga recusandae. + platforms: + - Iste autem veniam et enim unde. + - Facere quam. + - Rem quia consequuntur ut. + - Consequatur quos atque voluptates quo sit. + program_type: membership + targeting: Nulla ut laboriosam. + url: Modi asperiores ut libero vel. + responses: + "201": + description: Created response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Aliquid qui omnis et. + example: Asperiores recusandae deserunt ratione eius culpa sit. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief2' + example: + copy: Magni et rerum sed voluptatum. + event_details: Enim est occaecati sed non dolorem distinctio. + event_slug: Aperiam eum. + id: Omnis suscipit corrupti reiciendis. + keywords: Aut ratione sunt nostrum tenetur. + platforms: + - Dolorum aut distinctio vel. + - Veniam velit sequi sit impedit dolorum voluptas. + - Ipsum quaerat delectus cupiditate sit et. + program_type: Beatae soluta molestiae nihil quia omnis. + project_id: Perspiciatis saepe consequatur doloribus sequi et. + status: draft + targeting: Dolorem cum. + url: Quam et voluptatibus illum dolorem. + version: 1135469282733514180 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}: + delete: + tags: + - lfx-v2-campaign-service-briefs + summary: delete-brief lfx-v2-campaign-service-briefs + description: Archive a brief (soft delete). + operationId: lfx-v2-campaign-service-briefs#delete-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: b6261fa8-da85-4670-81bb-f0aa10d65871 + format: uuid + example: 4d147ddb-fca6-489e-a2a8-78921eadc560 + responses: + "204": + description: No Content response. + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-brief lfx-v2-campaign-service-briefs + description: Get a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 52099480-3594-4323-92b0-9a8e5a8fedcc + format: uuid + example: 6d67841a-f263-4343-acd9-5ea22a27e47d + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Libero repudiandae aliquid ex. + example: Eveniet suscipit odio ratione. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief3' + example: + copy: Dolores commodi aperiam voluptatibus. + event_details: Omnis quidem est non tenetur dolore. + event_slug: Distinctio ipsa nihil ad hic repellendus nobis. + id: Harum et minus voluptas. + keywords: Cupiditate voluptatem. + platforms: + - Cupiditate aliquam vel blanditiis sunt ut. + - Aperiam quo sint dolorum vel molestiae. + program_type: Porro ab id necessitatibus consequatur natus tempora. + project_id: Debitis labore aut. + status: approved + targeting: Et corporis officia sint. + url: Accusamus vero et voluptas accusamus sunt. + version: 1623908575818221398 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-brief lfx-v2-campaign-service-briefs + description: Replace a brief (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 7b75bd11-b6e0-441e-99e4-25f506ecfdd1 + format: uuid + example: 913052d6-1036-4a14-9f9a-107c3b258bb8 + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBriefRequestBody' + example: + brief: + copy: Blanditiis quidem qui iusto sed. + event_details: Dolor molestias ipsa dolorum est. + event_slug: Accusantium ad. + keywords: Vel fuga recusandae. + platforms: + - Iste autem veniam et enim unde. + - Facere quam. + - Rem quia consequuntur ut. + - Consequatur quos atque voluptates quo sit. + program_type: membership + targeting: Nulla ut laboriosam. + url: Modi asperiores ut libero vel. + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Ut aperiam eveniet nihil qui ea. + example: Id aut praesentium maiores unde quo culpa. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief4' + example: + copy: Doloremque et qui perferendis. + event_details: Fugit voluptas molestias architecto dicta quidem illo. + event_slug: Sapiente et et itaque dolores in. + id: Provident quia laboriosam magnam unde. + keywords: Quia aut sed fugiat adipisci eos rerum. + platforms: + - Nihil explicabo vero. + - Est qui asperiores velit enim. + - Expedita ut minus ut est recusandae aspernatur. + program_type: Rerum et. + project_id: Et sed nobis blanditiis. + status: approved + targeting: Ipsam temporibus sint et necessitatibus dolorem ut. + url: Rem dolores eligendi ea. + version: 6226287149251328788 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/approve: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: approve-brief lfx-v2-campaign-service-briefs + description: Approve a brief for campaign creation. + operationId: lfx-v2-campaign-service-briefs#approve-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 25175f34-92c0-434d-a72f-1a9c500ccdda + format: uuid + example: 78a49884-958d-4ef7-99b9-e60dab3c1338 + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Qui inventore. + example: Sunt cum ut. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief5' + example: + copy: Similique odio adipisci dolore iste. + event_details: Error laudantium. + event_slug: Dolorem autem ipsa autem provident est. + id: Pariatur voluptas odit fuga illo. + keywords: Error necessitatibus et autem doloribus est. + platforms: + - Et odio tenetur optio in ut sint. + - Iste eveniet laudantium rerum. + - Veritatis excepturi veniam maxime incidunt asperiores voluptatem. + - Nostrum dolorum iure eaque animi. + program_type: Numquam quia. + project_id: Sit ex commodi sapiente a illum. + status: archived + targeting: Maxime labore illo maxime provident. + url: Quo culpa. + version: 8076581276173981715 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-campaigns lfx-v2-campaign-service-briefs + description: Create campaigns across the selected platforms (async -> job). + operationId: lfx-v2-campaign-service-briefs#create-campaigns + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: d76e3514-7f04-404b-85c1-c9dd589109e7 + format: uuid + example: cc88b92c-5d67-435b-9340-3a62f5a9bab0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCampaignsRequestBody' + example: + input: + config: Facere et exercitationem. + platforms: + - microsoft-ads + - twitter-ads + - google-ads + responses: + "202": + description: Accepted response. + content: + application/json: + schema: + $ref: '#/components/schemas/JobCreateResponse' + example: + job_id: Et magni adipisci magnam veniam. + platforms: + - Odio et corporis. + - Dolorem sapiente. + - Aut natus. + status: queued + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-campaign lfx-v2-campaign-service-briefs + description: Get one campaign under a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 23c306fc-2672-44e3-a360-393f7d48f1dd + format: uuid + example: 6e627787-92a4-43ee-8c98-6b97ec8af1e5 + - name: campaign_id + in: path + description: Campaign UUID + required: true + schema: + type: string + description: Campaign UUID + example: f41342df-fae6-4961-b9b7-6a3dde13126b + format: uuid + example: dd6598b8-8da5-472a-bcbb-2d8f068036bc + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Debitis quis quidem quam corrupti. + example: Voluptatem labore excepturi quibusdam iure. + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign2' + example: + brief_id: Ex voluptas aspernatur aperiam voluptates aliquam. + campaign_name: Eaque ipsam harum nisi nobis et et. + id: Explicabo est totam qui praesentium ipsam. + platform: Ducimus neque. + platform_campaign_id: Aut perferendis natus. + project_id: Cum ipsum recusandae est adipisci ullam. + status: Excepturi corporis ut. + version: 9153915624091824945 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-campaign lfx-v2-campaign-service-briefs + description: Replace a campaign (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 8a332b11-4d31-48bf-8c47-17384ebd6445 + format: uuid + example: 05ce6f1f-49a7-45ce-9c2c-c1a771a99cab + - name: campaign_id + in: path + description: Campaign UUID + required: true + schema: + type: string + description: Campaign UUID + example: 66025a18-b20e-4a9f-b7ee-db46d0219499 + format: uuid + example: d0f0d2ab-d9dd-445b-bce5-1c29e2186349 + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCampaignRequestBody' + example: + campaign: + campaign_name: Ad voluptatem. + config: Et expedita itaque quos. + status: Tempore impedit. + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Molestiae qui nihil. + example: Ut facere magni qui veniam architecto. + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign3' + example: + brief_id: Et aut consequatur inventore enim reiciendis. + campaign_name: Ut mollitia accusantium dignissimos. + id: Ullam omnis autem. + platform: Neque ut rerum facilis quia molestiae ratione. + platform_campaign_id: Fugit provident officia officiis. + project_id: Id laboriosam nihil ea. + status: Consequatur qui molestiae velit ratione nesciunt et. + version: 476544424691753105 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] /projects/{project_id}/connection-google-ads: delete: tags: @@ -81,21 +1014,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Inventore ratione id vel iusto optio repellendus. - example: Rerum eveniet nam sunt. + example: Delectus voluptatem rerum et dolorum. + example: Quasi vel sint nisi laborum laboriosam assumenda. content: application/json: schema: $ref: '#/components/schemas/GoogleAdsConnection3' example: - account_id: Vitae qui. + account_id: Minima eaque earum. has_credentials: true - id: Minima autem libero natus id esse. - label: Odit eum commodi autem expedita dolorem. - login_customer_id: In laborum vel. - project_id: Sed adipisci eveniet et aut. + id: Excepturi deleniti voluptas aperiam vel. + label: Id temporibus et provident maiores neque quis. + login_customer_id: Assumenda ipsa dolor. + project_id: Laudantium amet veritatis rerum itaque consequatur laboriosam. status: active - version: 4120562596096857294 + version: 8240960881778207137 "404": description: 'NotFound: Resource not found' content: @@ -153,10 +1086,10 @@ paths: label: TLF Main login_customer_id: "9746983954" credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. responses: "201": description: Created response. @@ -166,21 +1099,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Occaecati quaerat eum quia a excepturi. - example: Inventore aut molestiae voluptas hic. + example: Porro eaque dolorem. + example: Sunt dolorum possimus eum. content: application/json: schema: $ref: '#/components/schemas/GoogleAdsConnection2' example: - account_id: Corrupti reprehenderit et non totam perspiciatis. - has_credentials: false - id: Harum laborum est adipisci sint. - label: Ipsa ex. - login_customer_id: Perspiciatis voluptatibus architecto velit. - project_id: Est impedit et tempore provident. + account_id: Nobis ut asperiores quo aut. + has_credentials: true + id: Nihil quae porro. + label: Nulla officia vitae perspiciatis eveniet. + login_customer_id: Dicta voluptate aut in. + project_id: Rem autem quae. status: error - version: 6280538112072962831 + version: 7743336992980724195 "400": description: 'BadRequest: Bad request' content: @@ -264,21 +1197,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Inventore impedit unde voluptatem quia ab recusandae. - example: Vel omnis iure. + example: Et non et. + example: Delectus vel. content: application/json: schema: $ref: '#/components/schemas/GoogleAdsConnection4' example: - account_id: Ut commodi asperiores. - has_credentials: false - id: Dignissimos non ut sed. - label: Perferendis nam voluptatibus totam. - login_customer_id: Voluptatem similique. - project_id: Deleniti quam. - status: inactive - version: 2550395787104322115 + account_id: Quidem odio ab accusantium aut est deleniti. + has_credentials: true + id: Odio incidunt distinctio sint. + label: Qui enim cumque saepe. + login_customer_id: Praesentium voluptatibus deserunt aliquam necessitatibus eos. + project_id: Aperiam quis quis reprehenderit non quae. + status: active + version: 5525678772225743935 "400": description: 'BadRequest: Bad request' content: @@ -360,10 +1293,10 @@ paths: $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' example: credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. responses: "204": description: No Content response. @@ -430,8 +1363,8 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Deserunt ab itaque amet numquam. - ok: true + message: Eligendi et. + ok: false "404": description: 'NotFound: Resource not found' content: @@ -535,24 +1468,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Necessitatibus quaerat minima a voluptatem ipsa et. - example: Pariatur perferendis explicabo expedita et velit accusamus. + example: Voluptatum explicabo vero repudiandae temporibus ut. + example: Est hic enim similique et eveniet vel. content: application/json: schema: $ref: '#/components/schemas/HubspotConnection3' example: - account_id: Perspiciatis saepe consequatur doloribus sequi et. - brand_kit: Distinctio vel. + account_id: In ipsa tempore incidunt velit provident perspiciatis. + brand_kit: Aspernatur voluptatem nobis. has_credentials: true - id: Nulla ut laboriosam. - label: Omnis suscipit corrupti reiciendis. - portal_id: Nihil quia omnis eaque aperiam eum. - project_id: Rerum delectus velit. - sender_email: Quam et voluptatibus illum dolorem. - sender_name: Eum dolorum. - status: active - version: 3995988906528985875 + id: Nihil sunt doloremque. + label: Ea architecto autem iste. + portal_id: Omnis ut ex sunt. + project_id: Ut ut odio odio sed minus quia. + sender_email: Beatae ut modi voluptatum veniam. + sender_name: Quibusdam consequuntur dolorem fugiat. + status: deleted + version: 5861862348723710898 "404": description: 'NotFound: Resource not found' content: @@ -606,14 +1539,14 @@ paths: $ref: '#/components/schemas/CreateHubspotRequestBody' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. responses: "201": description: Created response. @@ -623,24 +1556,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Sequi non et. - example: Quibusdam repellendus qui numquam. + example: Ex quis molestias nam quia facilis. + example: Deserunt earum saepe repellat. content: application/json: schema: $ref: '#/components/schemas/HubspotConnection2' - example: - account_id: Voluptates iste autem veniam et enim unde. - brand_kit: Blanditiis quidem qui iusto sed. - has_credentials: false - id: Nesciunt necessitatibus ex ea ullam corrupti sunt. - label: Modi asperiores ut libero vel. - portal_id: Rem quia consequuntur ut. - project_id: Omnis accusantium ad. - sender_email: Consequatur quos atque voluptates quo sit. - sender_name: Dolor molestias ipsa dolorum est. - status: inactive - version: 1691702820919785934 + example: + account_id: Porro ut et cupiditate. + brand_kit: Animi molestiae aut fugit alias nisi qui. + has_credentials: true + id: Eius dicta. + label: Non assumenda perspiciatis. + portal_id: Voluptatem vel. + project_id: Ullam distinctio dolore velit. + sender_email: Dolore repudiandae necessitatibus dolorem. + sender_name: Beatae quia minus vero at ut. + status: active + version: 2806381801577175312 "400": description: 'BadRequest: Bad request' content: @@ -712,12 +1645,12 @@ paths: $ref: '#/components/schemas/UpdateHubspotRequestBody' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. responses: "200": description: OK response. @@ -727,24 +1660,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Deleniti placeat rerum. - example: Recusandae totam soluta. + example: Blanditiis inventore similique est. + example: Possimus qui. content: application/json: schema: $ref: '#/components/schemas/HubspotConnection4' example: - account_id: Aut ratione sunt nostrum tenetur. - brand_kit: Ab laboriosam quis sapiente ut sit. - has_credentials: true - id: Ipsum quaerat delectus cupiditate sit et. - label: Magni et rerum sed voluptatum. - portal_id: Fugit enim et sint consectetur ea minus. - project_id: Enim est occaecati sed non dolorem distinctio. - sender_email: Ut laborum nostrum veniam qui rerum. - sender_name: Eum fugit repudiandae magnam ut. - status: active - version: 2378889005955898844 + account_id: Debitis nam. + brand_kit: Aut aut eos magnam quisquam et. + has_credentials: false + id: Eveniet sint voluptatibus quam dicta. + label: Quis iste eaque. + portal_id: Quia distinctio consequatur dolore. + project_id: Consequuntur voluptatem est qui. + sender_email: Corporis consequatur voluptatem voluptates. + sender_name: Reprehenderit provident repudiandae sit. + status: error + version: 7483108258641908951 "400": description: 'BadRequest: Bad request' content: @@ -826,7 +1759,7 @@ paths: $ref: '#/components/schemas/SetCredentialHubspotRequestBody' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. responses: "204": description: No Content response. @@ -893,7 +1826,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Tempora error rerum quae nihil. + message: Sunt aliquam et velit fuga quos est. ok: false "404": description: 'NotFound: Resource not found' @@ -998,21 +1931,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Ea eum. - example: Repudiandae quae aut. + example: Aspernatur enim perferendis. + example: Soluta porro distinctio cupiditate. content: application/json: schema: $ref: '#/components/schemas/LinkedinAdsConnection3' example: - account_id: Quis repellat tenetur. + account_id: Sed sed similique enim ea maiores. has_credentials: false - id: Et quam veritatis consequatur sed. - label: Ipsum fuga deleniti consequatur. - org_id: Natus laudantium quisquam. - project_id: Tempora aut incidunt incidunt dolorem eaque et. - status: inactive - version: 2296210645404105233 + id: In aperiam quam quis quam dolor. + label: Sit cupiditate nobis iste qui rerum. + org_id: Ratione expedita voluptatum commodi fuga ipsa quam. + project_id: Earum iusto. + status: active + version: 4984404630797976872 "404": description: 'NotFound: Resource not found' content: @@ -1067,10 +2000,10 @@ paths: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. responses: "201": description: Created response. @@ -1080,21 +2013,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Voluptatem doloremque ullam consequatur ut. - example: Ipsa magnam cum veritatis mollitia vel. + example: Molestias qui magnam impedit modi. + example: Ipsum ullam quis est incidunt debitis qui. content: application/json: schema: $ref: '#/components/schemas/LinkedinAdsConnection2' example: - account_id: Totam quasi vero. + account_id: Libero ut est rerum est occaecati et. has_credentials: false - id: Eum quia molestiae. - label: Est quos. - org_id: Est est id repellat quod deleniti cupiditate. - project_id: Rerum delectus tempora culpa. - status: error - version: 2086671994798543080 + id: Error nulla fuga fugit repellendus et accusamus. + label: Nam quia aperiam sunt est. + org_id: Consequuntur quod. + project_id: Non sequi error assumenda quam deleniti et. + status: inactive + version: 5045833847874249009 "400": description: 'BadRequest: Bad request' content: @@ -1167,7 +2100,7 @@ paths: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" responses: "200": @@ -1178,21 +2111,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Tempore rem quo fuga accusantium voluptas ratione. - example: Numquam optio possimus velit quod. + example: Quia sequi consequatur aut dolorem. + example: Necessitatibus quam. content: application/json: schema: $ref: '#/components/schemas/LinkedinAdsConnection4' example: - account_id: Ipsa rerum odit et nostrum veritatis dolores. + account_id: Neque facilis est qui possimus eligendi. has_credentials: false - id: Aut et voluptas ut officiis. - label: Debitis voluptatem quam occaecati. - org_id: Et tenetur sequi dolorum porro. - project_id: In autem tempore ut esse. - status: deleted - version: 7961829707957443728 + id: Consequuntur qui quidem exercitationem. + label: Sed numquam necessitatibus laudantium est ut. + org_id: Et nobis voluptas dignissimos exercitationem illum. + project_id: Ipsa quo vel architecto quas eligendi impedit. + status: inactive + version: 8080806383587730069 "400": description: 'BadRequest: Bad request' content: @@ -1274,7 +2207,7 @@ paths: $ref: '#/components/schemas/SetCredentialLinkedinAdsRequestBody' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. responses: "204": description: No Content response. @@ -1341,7 +2274,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Dolorum voluptates placeat rerum. + message: Ex est facilis qui ducimus modi ut. ok: true "404": description: 'NotFound: Resource not found' @@ -1446,22 +2379,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Vel necessitatibus vel voluptates tenetur veniam repellat. - example: Aut repudiandae voluptatibus odit autem. + example: Aut maiores ea omnis quia quas. + example: Sunt odit molestiae ab praesentium enim. content: application/json: schema: $ref: '#/components/schemas/MetaAdsConnection3' example: - account_id: Voluptatem cupiditate voluptas aliquam facilis iste sed. - app_id: A sed exercitationem ut. - has_credentials: false - id: Expedita unde molestiae. - label: Velit ullam. - page_id: Voluptatum a. - project_id: Voluptas dolor et incidunt pariatur quod odit. - status: active - version: 6376430019451136769 + account_id: Qui nobis et delectus quam. + app_id: Est dolor ut corporis. + has_credentials: true + id: Laboriosam qui id enim. + label: Nihil corrupti repellat eius. + page_id: Aut fuga. + project_id: Magni repudiandae. + status: deleted + version: 2782071096062379507 "404": description: 'NotFound: Resource not found' content: @@ -1516,12 +2449,12 @@ paths: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. responses: "201": description: Created response. @@ -1531,22 +2464,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Perspiciatis quam iste possimus ipsam. - example: Eveniet quia voluptatem impedit cumque doloribus. + example: Omnis ipsam ad sed laudantium ut ea. + example: Ad a et inventore tempora facilis dolores. content: application/json: schema: $ref: '#/components/schemas/MetaAdsConnection2' example: - account_id: Fuga ut expedita voluptatum sit. - app_id: Quia ut atque. + account_id: Ad inventore voluptatum eligendi quasi. + app_id: Placeat unde distinctio ducimus qui. has_credentials: true - id: Quis perspiciatis. - label: Similique vel nobis non molestias. - page_id: Velit et. - project_id: Nesciunt unde ullam occaecati maxime. - status: active - version: 8123871071866488705 + id: Deleniti voluptates non sint inventore. + label: Porro voluptate. + page_id: Aperiam velit dolores. + project_id: Iure ut nostrum facilis suscipit. + status: inactive + version: 358292288538552244 "400": description: 'BadRequest: Bad request' content: @@ -1619,9 +2552,9 @@ paths: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. responses: "200": description: OK response. @@ -1631,22 +2564,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Dicta qui. - example: Voluptate dolorum id ut molestias id. + example: Natus ratione. + example: Amet quam consequuntur nulla quaerat. content: application/json: schema: $ref: '#/components/schemas/MetaAdsConnection4' example: - account_id: Quos quo eum dignissimos iure praesentium. - app_id: Voluptas ratione delectus ut adipisci. + account_id: Quos hic doloribus. + app_id: Qui a necessitatibus molestiae qui unde. has_credentials: false - id: Doloremque itaque eius magni autem. - label: Ex tempore. - page_id: Qui mollitia perferendis eveniet voluptas est et. - project_id: Ullam tenetur quia totam magni sunt repellat. - status: inactive - version: 7772855560046758577 + id: Illo vitae occaecati omnis. + label: Explicabo possimus consequatur. + page_id: Eos velit eum et sint corrupti. + project_id: Pariatur debitis. + status: deleted + version: 3363918130362824521 "400": description: 'BadRequest: Bad request' content: @@ -1728,8 +2661,8 @@ paths: $ref: '#/components/schemas/SetCredentialMetaAdsRequestBody' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. responses: "204": description: No Content response. @@ -1796,8 +2729,8 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Aliquam animi unde. - ok: true + message: Dolorem qui explicabo consequuntur repellat non suscipit. + ok: false "404": description: 'NotFound: Resource not found' content: @@ -1901,21 +2834,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Non eos. - example: Enim voluptatem inventore quasi. + example: Quidem est. + example: Corporis deserunt quae. content: application/json: schema: $ref: '#/components/schemas/MicrosoftAdsConnection3' example: - account_id: Optio dolorem omnis. - customer_id: Rerum adipisci minima quibusdam autem accusantium. + account_id: Dolor ut velit quam omnis et dolor. + customer_id: Vitae vel omnis autem accusamus mollitia. has_credentials: false - id: Veniam eum ut libero quibusdam debitis. - label: Commodi quam voluptates. - project_id: Ut consectetur. + id: Maxime dicta qui aut. + label: Velit et impedit facilis quaerat. + project_id: Suscipit eos nam ipsa. status: deleted - version: 6203614951491514230 + version: 443094643112211074 "404": description: 'NotFound: Resource not found' content: @@ -1969,14 +2902,14 @@ paths: $ref: '#/components/schemas/CreateMicrosoftAdsRequestBody' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Nisi blanditiis eveniet quasi ea. + client_secret: Ut corporis dolorum consequatur eius perferendis nobis. + developer_token: Et et itaque sunt. + refresh_token: Vel voluptate necessitatibus. responses: "201": description: Created response. @@ -1986,21 +2919,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Delectus enim quas id porro. - example: Quo repudiandae. + example: Sint expedita fugit iste nemo voluptas. + example: Fugit unde ea quis. content: application/json: schema: $ref: '#/components/schemas/MicrosoftAdsConnection2' example: - account_id: Exercitationem rerum iste ducimus. - customer_id: Quidem velit est explicabo est sed. + account_id: Repellendus porro et distinctio. + customer_id: Quia eaque. has_credentials: true - id: Modi rerum sint. - label: Dolorem dolor fugiat quia. - project_id: Labore iste voluptates quos. - status: inactive - version: 8271015532862321776 + id: Labore omnis provident nihil aut rerum est. + label: Sint non. + project_id: Amet rem ab. + status: error + version: 5425752825388180685 "400": description: 'BadRequest: Bad request' content: @@ -2072,9 +3005,9 @@ paths: $ref: '#/components/schemas/UpdateMicrosoftAdsRequestBody' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. responses: "200": description: OK response. @@ -2084,21 +3017,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Aut omnis itaque ut vero voluptatibus. - example: Vero dolorem qui. + example: Aut mollitia. + example: Pariatur ratione blanditiis. content: application/json: schema: $ref: '#/components/schemas/MicrosoftAdsConnection4' example: - account_id: Maxime fugiat illo. - customer_id: Minus est ut. + account_id: Esse voluptate eum laboriosam distinctio non. + customer_id: Id aut et adipisci quo ut. has_credentials: false - id: Tempora recusandae. - label: Reiciendis architecto atque maxime soluta dolorem ducimus. - project_id: Itaque harum. - status: deleted - version: 6177609042943350629 + id: Rem quia vel ipsam dolores. + label: Ullam facilis ab sapiente voluptatem. + project_id: Et qui ut libero numquam. + status: inactive + version: 8626906144120400240 "400": description: 'BadRequest: Bad request' content: @@ -2180,10 +3113,10 @@ paths: $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' example: credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Nisi blanditiis eveniet quasi ea. + client_secret: Ut corporis dolorum consequatur eius perferendis nobis. + developer_token: Et et itaque sunt. + refresh_token: Vel voluptate necessitatibus. responses: "204": description: No Content response. @@ -2250,7 +3183,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Ullam fugiat. + message: Voluptates ut culpa saepe necessitatibus. ok: true "404": description: 'NotFound: Resource not found' @@ -2355,20 +3288,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Autem aliquam voluptatibus assumenda mollitia molestias eos. - example: Fugit eum et sint possimus omnis. + example: Consequatur beatae. + example: Totam dolorem. content: application/json: schema: $ref: '#/components/schemas/RedditAdsConnection3' example: - account_id: Amet iusto provident sequi est. - has_credentials: true - id: Voluptas non accusantium. - label: Sit dolorum excepturi quibusdam. - project_id: Architecto consequatur enim magnam eius voluptas voluptatibus. - status: deleted - version: 1322493338651594921 + account_id: Iusto omnis. + has_credentials: false + id: Odio totam cum deleniti placeat fuga. + label: Et ut quis ut. + project_id: Saepe voluptatem reiciendis pariatur. + status: error + version: 757606501996618102 "404": description: 'NotFound: Resource not found' content: @@ -2423,11 +3356,11 @@ paths: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. responses: "201": description: Created response. @@ -2437,20 +3370,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Ipsa aut. - example: Impedit vitae molestiae. + example: Iure tempora non ea facilis nihil eius. + example: Corporis nesciunt ipsam voluptates magnam atque. content: application/json: schema: $ref: '#/components/schemas/RedditAdsConnection2' example: - account_id: Ut ea alias. - has_credentials: false - id: Explicabo cupiditate rerum. - label: Hic vero et. - project_id: Incidunt ipsum est perspiciatis soluta voluptas est. + account_id: Non laudantium sunt minima repudiandae illo harum. + has_credentials: true + id: Atque laudantium. + label: Enim accusantium labore esse numquam. + project_id: Rerum earum rerum adipisci. status: inactive - version: 347677279926224703 + version: 6816002535409129912 "400": description: 'BadRequest: Bad request' content: @@ -2523,7 +3456,7 @@ paths: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. responses: "200": description: OK response. @@ -2533,20 +3466,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Neque hic voluptatem ad similique sunt. - example: Ratione impedit recusandae dolor consequatur. + example: Aut enim. + example: Voluptas a natus est dolor voluptatum. content: application/json: schema: $ref: '#/components/schemas/RedditAdsConnection4' example: - account_id: Saepe pariatur. + account_id: Et rerum in expedita. has_credentials: true - id: Vero quam. - label: Eveniet soluta adipisci. - project_id: Neque tempore voluptatibus quo quod nesciunt. + id: Voluptatum ducimus tempore quae eum consequatur dolore. + label: Quo veritatis quis delectus est. + project_id: Illum illum dolores tenetur ut illum. status: active - version: 779913646680513111 + version: 4673192311933251173 "400": description: 'BadRequest: Bad request' content: @@ -2628,9 +3561,9 @@ paths: $ref: '#/components/schemas/SetCredentialRedditAdsRequestBody' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. responses: "204": description: No Content response. @@ -2697,7 +3630,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Repudiandae veniam soluta alias natus aut. + message: Assumenda nesciunt distinctio eaque. ok: true "404": description: 'NotFound: Resource not found' @@ -2802,21 +3735,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Enim esse dolores. - example: Est perferendis eos et cumque nulla. + example: Odio autem perferendis accusamus nulla placeat aut. + example: Aliquid quia natus. content: application/json: schema: $ref: '#/components/schemas/TwitterAdsConnection3' example: - account_id: Et tempore. - funding_instrument_id: Est et id laudantium magnam ratione. - has_credentials: false - id: Ipsum deserunt ut. - label: Ut nihil quo tenetur doloribus fugiat natus. - project_id: Sed ea. - status: error - version: 6360932954176398686 + account_id: Id unde veritatis officiis aut fugiat. + funding_instrument_id: Voluptas tenetur nobis facere. + has_credentials: true + id: Eaque voluptate reprehenderit sed voluptatem sed illo. + label: Cum nisi numquam. + project_id: Praesentium tenetur quas at ut necessitatibus dolor. + status: active + version: 4642810346647163563 "404": description: 'NotFound: Resource not found' content: @@ -2871,13 +3804,13 @@ paths: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. responses: "201": description: Created response. @@ -2887,21 +3820,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Vel temporibus sunt nulla esse sed. - example: Facere sed ea qui pariatur adipisci. + example: Sit dignissimos est. + example: Harum esse. content: application/json: schema: $ref: '#/components/schemas/TwitterAdsConnection2' example: - account_id: Totam sequi. - funding_instrument_id: Labore ut iusto dolores. - has_credentials: false - id: Dolor ut. - label: Porro sed minus illo rerum ut. - project_id: Veniam maiores dolorum sit et. - status: active - version: 5887915076445143491 + account_id: Vitae aut ea. + funding_instrument_id: Aut sit architecto ut unde saepe. + has_credentials: true + id: Non iste ad rem quidem sunt qui. + label: Deleniti doloribus debitis assumenda dignissimos. + project_id: Ullam ab suscipit et labore harum. + status: error + version: 753723066786334950 "400": description: 'BadRequest: Bad request' content: @@ -2974,8 +3907,8 @@ paths: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. responses: "200": description: OK response. @@ -2985,21 +3918,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Neque maiores quo aliquam in rem. - example: Voluptatem possimus veniam dignissimos consequatur. + example: Voluptatibus ut. + example: Fugit nostrum rerum. content: application/json: schema: $ref: '#/components/schemas/TwitterAdsConnection4' example: - account_id: Est dolorem eos amet dolorem sed. - funding_instrument_id: Quia porro. + account_id: Deserunt repellendus corrupti itaque quo sit. + funding_instrument_id: Eum et ipsum expedita reprehenderit eos. has_credentials: false - id: Ab nobis fuga atque inventore fugiat. - label: Quod blanditiis et. - project_id: Laboriosam maxime facere culpa modi. - status: error - version: 796228352115418506 + id: Blanditiis mollitia dolores illo nostrum magnam. + label: Omnis nobis. + project_id: Nostrum tempore. + status: inactive + version: 6605548766358472787 "400": description: 'BadRequest: Bad request' content: @@ -3081,10 +4014,10 @@ paths: $ref: '#/components/schemas/SetCredentialTwitterAdsRequestBody' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. responses: "204": description: No Content response. @@ -3151,7 +4084,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Quo aliquid nobis. + message: Ut architecto deleniti nostrum sed aperiam. ok: true "404": description: 'NotFound: Resource not found' @@ -3182,25 +4115,806 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] + /projects/{project_id}/jobs/{job_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-job lfx-v2-campaign-service-briefs + description: Poll campaign-creation job status. + operationId: lfx-v2-campaign-service-briefs#get-job + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: job_id + in: path + description: Job UUID + required: true + schema: + type: string + description: Job UUID + example: 79c02b27-dc40-4b89-9073-64a5596694c3 + format: uuid + example: 52112f6b-ba24-497d-8dd7-e7c129c8add5 + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/JobPollResponse' + example: + error: Beatae est officia et sint. + job_id: Et dignissimos ex. + result: + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + - campaign_id: Et voluptatem voluptatem voluptatem. + error: Omnis doloribus qui. + ok: true + platform: Nobis velit in. + status: running + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] components: schemas: BadRequestError: type: object properties: - code: + code: + type: string + description: HTTP status code + example: "400" + message: + type: string + description: Error message + example: The request was invalid. + example: + code: "400" + message: The request was invalid. + required: + - code + - message + Brief: + type: object + properties: + copy: + description: Ad copy + example: Vitae illo consequuntur esse. + etag: + type: string + description: ETag header value (mirrors version) + example: Officia natus quia labore. + event_details: + description: Extracted event/course details + example: Quia recusandae dignissimos dolore harum totam. + event_slug: + type: string + description: Event/course slug + example: Molestiae eius in reprehenderit nobis hic. + id: + type: string + description: Brief UUID + example: Aliquid dignissimos minima aut fugit. + keywords: + description: Keyword list + example: Libero recusandae ut ullam aliquam. + platforms: + type: array + items: + type: string + example: Harum aliquam sunt eum. + description: Suggested default platforms + example: + - Occaecati facere voluptatibus nihil nisi. + - Distinctio tempore qui velit omnis dolorem est. + program_type: + type: string + description: Funnel context + example: Ipsum neque sapiente aliquid quidem velit. + project_id: + type: string + description: Owning project + example: Et iure laudantium quia est molestiae. + status: + type: string + description: Lifecycle status + example: draft + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Iure consequatur minus nostrum iste nemo. + url: + type: string + description: Event/course page URL + example: Magni eos voluptatem sint hic ut. + version: + type: integer + description: Optimistic-concurrency version + example: 3221003988771868872 + format: int64 + example: + copy: Quod exercitationem. + etag: Enim nam id. + event_details: Repellendus eveniet veritatis impedit aut iste earum. + event_slug: Sequi ea ut vero consequatur possimus. + id: Ut dolores et incidunt. + keywords: Pariatur saepe ab qui et. + platforms: + - Et magni ad et provident. + - Non quisquam iure. + - Est ut aliquid. + program_type: Ipsam tempora est. + project_id: Ipsa fugiat assumenda. + status: archived + targeting: Laboriosam sunt quis omnis animi. + url: Assumenda sit delectus veniam. + version: 3928799509074172783 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief2: + type: object + properties: + copy: + description: Ad copy + example: Sit facere iste et alias. + event_details: + description: Extracted event/course details + example: Ut iste et aspernatur delectus. + event_slug: + type: string + description: Event/course slug + example: Aspernatur culpa sed non qui ad. + id: + type: string + description: Brief UUID + example: Perspiciatis ea dolor. + keywords: + description: Keyword list + example: Magni ad doloremque soluta et quam totam. + platforms: + type: array + items: + type: string + example: Rerum voluptatem saepe eligendi eum non. + description: Suggested default platforms + example: + - Totam occaecati enim eaque blanditiis deleniti reprehenderit. + - Quas commodi. + - Qui mollitia architecto recusandae voluptates excepturi reiciendis. + - Consequatur amet dolorem qui. + program_type: + type: string + description: Funnel context + example: Ratione dicta. + project_id: + type: string + description: Owning project + example: Dolores qui. + status: + type: string + description: Lifecycle status + example: archived + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Eos repudiandae voluptate est enim aut laborum. + url: + type: string + description: Event/course page URL + example: Voluptas qui. + version: + type: integer + description: Optimistic-concurrency version + example: 7978828505057037537 + format: int64 + example: + copy: Autem aperiam quis. + event_details: Quae qui optio magni itaque iure. + event_slug: Consequatur velit atque nam voluptatem in. + id: Est et beatae. + keywords: Enim odit numquam. + platforms: + - Ea ut voluptatem velit. + - Aut est est eum ut ut. + - Et molestiae blanditiis nihil dolor exercitationem. + - Placeat dolores culpa sed. + program_type: Repellendus autem ullam odio quis. + project_id: Est officiis enim qui. + status: draft + targeting: Placeat aut dolores at. + url: Explicabo voluptatem et. + version: 2226888956028863556 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief3: + type: object + properties: + copy: + description: Ad copy + example: Quia amet vero dolorum incidunt officia nihil. + event_details: + description: Extracted event/course details + example: Optio enim nisi doloremque rerum sed consequatur. + event_slug: + type: string + description: Event/course slug + example: Ut aspernatur et ad. + id: + type: string + description: Brief UUID + example: Dignissimos voluptatem et labore sit atque qui. + keywords: + description: Keyword list + example: Dolores dignissimos perferendis tempore eum laboriosam iure. + platforms: + type: array + items: + type: string + example: Vel aut rerum voluptatem ad minus. + description: Suggested default platforms + example: + - Cum nemo esse nobis. + - Aut dolorem ea veniam. + - Quae adipisci doloribus ullam. + - Dolor eum et consequatur. + program_type: + type: string + description: Funnel context + example: Unde dolore id a necessitatibus fuga ducimus. + project_id: + type: string + description: Owning project + example: Aut eaque eveniet autem. + status: + type: string + description: Lifecycle status + example: draft + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: At magnam saepe incidunt nisi est. + url: + type: string + description: Event/course page URL + example: Magnam sequi esse at. + version: + type: integer + description: Optimistic-concurrency version + example: 3943386503686697829 + format: int64 + example: + copy: Quos labore est ut ratione. + event_details: Sint molestiae occaecati. + event_slug: Deleniti non in iure dolores et quis. + id: Distinctio possimus. + keywords: Ut occaecati tenetur minima. + platforms: + - Minus natus. + - Doloribus nam eius nulla suscipit ullam accusamus. + program_type: Id ducimus excepturi est et est. + project_id: Aliquid qui facilis neque sed alias. + status: archived + targeting: Sed impedit. + url: Recusandae similique assumenda ut quia. + version: 5475183181677687295 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief4: + type: object + properties: + copy: + description: Ad copy + example: Laborum quia beatae non et excepturi. + event_details: + description: Extracted event/course details + example: Molestias voluptatibus nam debitis hic qui. + event_slug: + type: string + description: Event/course slug + example: Exercitationem voluptatum perspiciatis autem. + id: + type: string + description: Brief UUID + example: Ut ut qui sed explicabo dolorem totam. + keywords: + description: Keyword list + example: Minus officiis et quisquam dicta rerum. + platforms: + type: array + items: + type: string + example: Sunt labore voluptate. + description: Suggested default platforms + example: + - Ipsum quo possimus saepe quam. + - Qui cum. + program_type: + type: string + description: Funnel context + example: Vero nulla laboriosam vel et et iure. + project_id: + type: string + description: Owning project + example: Qui nesciunt sed magnam. + status: + type: string + description: Lifecycle status + example: archived + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Enim est libero enim quo. + url: + type: string + description: Event/course page URL + example: Eum ut. + version: + type: integer + description: Optimistic-concurrency version + example: 888507263129682759 + format: int64 + example: + copy: Et fuga veniam. + event_details: Aliquid eum itaque qui recusandae recusandae. + event_slug: Illum expedita commodi in dolores nemo similique. + id: Dolorem neque assumenda sed sapiente aspernatur ipsam. + keywords: Ea et iusto doloremque odio pariatur. + platforms: + - Voluptatem libero quia. + - Harum molestiae cupiditate ab et eum ex. + - Quia velit adipisci. + program_type: Dignissimos in tempore quo consequatur sint qui. + project_id: Qui illo totam. + status: archived + targeting: Expedita animi ut velit at. + url: Autem impedit. + version: 6980228077781980481 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief5: + type: object + properties: + copy: + description: Ad copy + example: Quo soluta maxime temporibus earum et aperiam. + event_details: + description: Extracted event/course details + example: Amet officia occaecati quam nostrum quis voluptas. + event_slug: + type: string + description: Event/course slug + example: Sapiente alias et error nobis minima. + id: + type: string + description: Brief UUID + example: Ratione est omnis voluptas praesentium. + keywords: + description: Keyword list + example: Ab itaque tempore vel quia cum quas. + platforms: + type: array + items: + type: string + example: Fuga maxime tempora rem. + description: Suggested default platforms + example: + - Debitis rerum. + - Dolor pariatur. + program_type: + type: string + description: Funnel context + example: Laboriosam et. + project_id: + type: string + description: Owning project + example: Aspernatur voluptatum facilis. + status: + type: string + description: Lifecycle status + example: approved + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Minus sit necessitatibus. + url: + type: string + description: Event/course page URL + example: Reprehenderit dolorem impedit qui suscipit ipsa ut. + version: + type: integer + description: Optimistic-concurrency version + example: 4889810267187914144 + format: int64 + example: + copy: Excepturi cumque ullam esse. + event_details: Quia voluptates voluptates nisi nihil aut molestias. + event_slug: Sed commodi voluptatibus. + id: Et est debitis ea. + keywords: Non autem voluptatem ut. + platforms: + - Facilis asperiores enim nisi. + - Perspiciatis optio. + - Quia doloribus omnis et excepturi fugit dolores. + program_type: Nemo molestiae modi ut non atque. + project_id: Eum voluptatum accusamus illo eveniet eum. + status: approved + targeting: Distinctio non recusandae et qui ut exercitationem. + url: Nobis est fugit ratione consectetur exercitationem. + version: 1659901820906198210 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + BriefInput: + type: object + properties: + copy: + description: Ad copy + example: Tempora voluptate fuga ea distinctio. + event_details: + description: Extracted event/course details + example: Pariatur mollitia incidunt autem. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Quis harum recusandae. + keywords: + description: Keyword list + example: Mollitia aut tempore rerum. + platforms: + type: array + items: + type: string + example: Repellat eum totam voluptatem eos doloribus. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Eligendi magni quidem sit in quia. + - Perspiciatis alias et amet ex molestiae. + - Minus libero et consequuntur suscipit. + program_type: + type: string + description: Funnel context + example: membership + enum: + - events + - education + - membership + targeting: + description: Targeting recommendation + example: Eaque nesciunt. + url: + type: string + description: Event/course page URL + example: Ipsum est amet quaerat. + example: + copy: Beatae consectetur aut omnis labore et quis. + event_details: Natus deleniti est natus. + event_slug: Inventore in eligendi ipsam. + keywords: Dolores repellat. + platforms: + - Itaque ut dolores in. + - Expedita placeat dolores numquam molestias. + - Aliquid deleniti fuga est aliquam. + program_type: education + targeting: Similique omnis. + url: Vel atque eos reiciendis sit minima non. + required: + - program_type + - event_slug + Campaign: + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Voluptas necessitatibus cumque non porro qui molestias. + campaign_name: + type: string + description: Campaign name + example: Nulla perferendis possimus deserunt iure et. + etag: + type: string + description: ETag header value (mirrors version) + example: Omnis nesciunt qui. + id: + type: string + description: Campaign UUID + example: Et minima dolorem sit. + platform: + type: string + description: Channel + example: In praesentium cumque. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Pariatur odio molestias voluptatum. + project_id: + type: string + description: Owning project + example: Tempora qui. + status: + type: string + description: Campaign status + example: Ea quidem ducimus tempora ea molestias. + version: + type: integer + description: Optimistic-concurrency version + example: 971659538665007246 + format: int64 + example: + brief_id: Odit provident sed quo aperiam. + campaign_name: Voluptatem repellat expedita inventore. + etag: Maxime hic aliquam ratione ipsum. + id: Quia fuga atque. + platform: Aspernatur nihil et incidunt dolorem dolor sed. + platform_campaign_id: Delectus harum voluptates sit est corporis. + project_id: Sequi culpa. + status: Necessitatibus ut necessitatibus in. + version: 5532967292288417247 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + Campaign2: + type: object + properties: + brief_id: type: string - description: HTTP status code - example: "400" - message: + description: Parent brief + example: Et iste. + campaign_name: type: string - description: Error message - example: The request was invalid. + description: Campaign name + example: Rerum reiciendis similique. + id: + type: string + description: Campaign UUID + example: Ea laboriosam totam unde. + platform: + type: string + description: Channel + example: Voluptas ut nostrum officiis et accusamus provident. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Odio eos earum fuga non est. + project_id: + type: string + description: Owning project + example: Ipsa sint optio nobis quae corporis. + status: + type: string + description: Campaign status + example: Tempora qui incidunt tempora. + version: + type: integer + description: Optimistic-concurrency version + example: 7916176946868712323 + format: int64 example: - code: "400" - message: The request was invalid. + brief_id: Voluptas incidunt eligendi consequatur. + campaign_name: Earum temporibus ipsum necessitatibus ratione possimus eum. + id: Voluptatem dolores tempora qui sit. + platform: Labore eos et repudiandae odit omnis cupiditate. + platform_campaign_id: Illo repellat et cum. + project_id: Voluptates soluta animi sit ut. + status: Repellat eveniet reiciendis consequatur consequatur sint. + version: 2970088391723074573 required: - - code - - message + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + Campaign3: + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Et et sed. + campaign_name: + type: string + description: Campaign name + example: Minima adipisci aut quia quisquam. + id: + type: string + description: Campaign UUID + example: Molestiae quo non. + platform: + type: string + description: Channel + example: Vel quaerat iure ipsam. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Necessitatibus reprehenderit illum. + project_id: + type: string + description: Owning project + example: Reprehenderit natus. + status: + type: string + description: Campaign status + example: Quia facilis sit. + version: + type: integer + description: Optimistic-concurrency version + example: 7577275663228988931 + format: int64 + example: + brief_id: Molestiae cupiditate quis modi blanditiis. + campaign_name: Distinctio dignissimos. + id: Nihil qui doloremque nam magnam aliquid impedit. + platform: Tempora quod expedita magni. + platform_campaign_id: Harum ipsa. + project_id: Accusantium expedita alias placeat incidunt sit perspiciatis. + status: Voluptatibus natus tempora commodi et. + version: 6630207652424081561 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + CampaignCreateInput: + type: object + properties: + config: + description: Per-platform campaign configuration + example: Impedit ullam eaque suscipit harum sint neque. + platforms: + type: array + items: + type: string + example: twitter-ads + enum: + - google-ads + - linkedin-ads + - meta-ads + - reddit-ads + - twitter-ads + - microsoft-ads + - hubspot + description: Platforms to create campaigns on (binding selection) + example: + - meta-ads + - google-ads + - reddit-ads + minItems: 1 + example: + config: Optio consequatur nulla sint qui alias voluptatem. + platforms: + - linkedin-ads + - reddit-ads + - google-ads + required: + - platforms + CampaignUpdateInput: + type: object + properties: + campaign_name: + type: string + description: Campaign name + example: Amet hic est. + config: + description: Campaign configuration snapshot + example: Quae esse mollitia et dolorum. + status: + type: string + description: Campaign status + example: Quaerat occaecati rerum. + example: + campaign_name: Aspernatur quos est magnam voluptates. + config: Dolores dolore id dolore sed. + status: Libero autem quod saepe ullam ipsum. + required: + - campaign_name + - status ConflictError: type: object properties: @@ -3241,16 +4955,51 @@ components: message: type: string description: Human-readable detail - example: Rerum sunt eum et qui. + example: Accusantium totam quia. ok: type: boolean description: Whether the credential authenticated against the provider example: true example: - message: Ut quia harum. + message: Debitis adipisci. ok: false required: - ok + CreateBriefRequestBody: + type: object + properties: + brief: + $ref: '#/components/schemas/BriefInput' + example: + brief: + copy: Blanditiis quidem qui iusto sed. + event_details: Dolor molestias ipsa dolorum est. + event_slug: Accusantium ad. + keywords: Vel fuga recusandae. + platforms: + - Iste autem veniam et enim unde. + - Facere quam. + - Rem quia consequuntur ut. + - Consequatur quos atque voluptates quo sit. + program_type: membership + targeting: Nulla ut laboriosam. + url: Modi asperiores ut libero vel. + required: + - brief + CreateCampaignsRequestBody: + type: object + properties: + input: + $ref: '#/components/schemas/CampaignCreateInput' + example: + input: + config: Facere et exercitationem. + platforms: + - microsoft-ads + - twitter-ads + - google-ads + required: + - input CreateGoogleAdsRequestBody: type: object properties: @@ -3264,10 +5013,10 @@ components: label: TLF Main login_customer_id: "9746983954" credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. required: - config - credentials @@ -3280,14 +5029,14 @@ components: $ref: '#/components/schemas/HubspotCredentials' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. required: - config - credentials @@ -3301,10 +5050,10 @@ components: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. required: - config - credentials @@ -3318,12 +5067,12 @@ components: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. required: - config - credentials @@ -3336,14 +5085,14 @@ components: $ref: '#/components/schemas/MicrosoftAdsCredentials' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Nisi blanditiis eveniet quasi ea. + client_secret: Ut corporis dolorum consequatur eius perferendis nobis. + developer_token: Et et itaque sunt. + refresh_token: Vel voluptate necessitatibus. required: - config - credentials @@ -3357,11 +5106,11 @@ components: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. required: - config - credentials @@ -3375,13 +5124,13 @@ components: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. required: - config - credentials @@ -3391,35 +5140,35 @@ components: account_id: type: string description: Provider account identifier - example: Voluptatem sed quia excepturi. + example: Voluptatibus aliquam et. etag: type: string description: ETag header value (mirrors version) - example: Voluptas ullam dolores rem. + example: Mollitia dolorem. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Labore laudantium. + example: Modi consequatur molestias et et deleniti. label: type: string description: Optional friendly name - example: Eos voluptatibus. + example: Atque hic quam impedit placeat. login_customer_id: type: string description: Manager account used for API access - example: Officia quidem non qui voluptates natus cum. + example: Consequatur enim voluptas numquam aut et. project_id: type: string description: Owning project - example: Facilis reiciendis dignissimos consequatur iste qui minima. + example: Reiciendis odit eos et quidem. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -3428,18 +5177,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1885605882290356239 + example: 2538353845791645445 format: int64 example: - account_id: Iusto velit quo voluptatibus modi reiciendis aliquam. - etag: Sunt recusandae similique. - has_credentials: false - id: Autem recusandae totam. - label: Odit necessitatibus veritatis amet ut rerum quidem. - login_customer_id: Iusto quia. - project_id: Rerum nisi. - status: error - version: 4043658872882310311 + account_id: Rem fugit accusantium est. + etag: Dolorem asperiores autem aut aut soluta. + has_credentials: true + id: Eius dolor magnam necessitatibus amet. + label: Rerum tempore tempora. + login_customer_id: Et est et corporis. + project_id: Earum aut qui. + status: active + version: 6983147448769937945 required: - id - project_id @@ -3454,31 +5203,31 @@ components: account_id: type: string description: Provider account identifier - example: Error ipsam maiores et maiores laudantium aut. + example: Sed ipsam enim esse iure est. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Aliquid harum quis. + example: Minima dolorem omnis aspernatur veritatis adipisci. label: type: string description: Optional friendly name - example: Mollitia odio et dolor et consectetur. + example: Voluptas ipsam iste. login_customer_id: type: string description: Manager account used for API access - example: Sint inventore iste iure ut. + example: Quis quod aut sunt ex et autem. project_id: type: string description: Owning project - example: Rerum eaque eius est rerum. + example: Repellendus adipisci sit et enim quasi ut. status: type: string description: Connection status - example: inactive + example: active enum: - active - inactive @@ -3487,17 +5236,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1681601347963415661 + example: 5457180552368415477 format: int64 example: - account_id: Ipsa et dolore. + account_id: Natus aliquam aut quas aliquid. has_credentials: true - id: Facilis suscipit. - label: Ad inventore voluptatum eligendi quasi. - login_customer_id: Placeat unde distinctio ducimus qui. - project_id: Porro voluptate. - status: inactive - version: 1820304569870851201 + id: Non adipisci omnis veritatis omnis. + label: Placeat et. + login_customer_id: Laboriosam praesentium beatae quis. + project_id: Omnis reprehenderit. + status: active + version: 5939335900418206229 required: - id - project_id @@ -3511,27 +5260,27 @@ components: account_id: type: string description: Provider account identifier - example: Nihil corrupti repellat eius. + example: Vero provident voluptas consequatur iusto. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Dignissimos non sed omnis officiis et. + example: Eius harum. label: type: string description: Optional friendly name - example: Magni repudiandae. + example: Nihil excepturi provident saepe aliquam architecto. login_customer_id: type: string description: Manager account used for API access - example: Delectus quam et eos. + example: Ut magnam aut illum commodi placeat sed. project_id: type: string description: Owning project - example: Laboriosam qui id enim. + example: Qui est nemo ullam. status: type: string description: Connection status @@ -3544,17 +5293,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 8134511901185890068 + example: 8077434258112236542 format: int64 example: - account_id: Illo vitae occaecati omnis. + account_id: Voluptas quasi esse corporis. has_credentials: false - id: Rem aut fuga. - label: Cupiditate ut illo dolor quaerat totam. - login_customer_id: Explicabo possimus consequatur. - project_id: Est dolor ut corporis. + id: Minus pariatur praesentium amet. + label: Sint velit earum laboriosam dignissimos reiciendis et. + login_customer_id: Deserunt reiciendis dolorem totam voluptatem. + project_id: Libero pariatur ut. status: active - version: 299946558233755685 + version: 6561992588765026566 required: - id - project_id @@ -3568,27 +5317,27 @@ components: account_id: type: string description: Provider account identifier - example: A necessitatibus molestiae. + example: Laudantium doloremque aut eligendi perferendis aut. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Quos hic doloribus. + example: Amet qui provident. label: type: string description: Optional friendly name - example: Eum et sint corrupti eos. + example: Ut ea. login_customer_id: type: string description: Manager account used for API access - example: Dolorem qui explicabo consequuntur repellat non suscipit. + example: Ea consectetur delectus. project_id: type: string description: Owning project - example: Neque autem ex eos. + example: Repudiandae molestias est consectetur porro quo pariatur. status: type: string description: Connection status @@ -3601,17 +5350,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7747776820204091337 + example: 10157424754048271 format: int64 example: - account_id: Sit est dolores est et. - has_credentials: false - id: Sit tempore. - label: Ut quo iure. - login_customer_id: Velit atque laudantium ullam rerum. - project_id: Magnam ut similique ea minima magnam. - status: deleted - version: 7309957715046801903 + account_id: Aut earum rerum ducimus dicta velit. + has_credentials: true + id: Id officiis architecto. + label: Sit iste ea. + login_customer_id: Molestiae dolorum non perspiciatis a. + project_id: Et libero ea aut omnis. + status: error + version: 8687544223346888800 required: - id - project_id @@ -3646,25 +5395,25 @@ components: client_id: type: string description: OAuth client id - example: Ut aut. + example: Sint autem repudiandae asperiores et. client_secret: type: string description: OAuth client secret - example: Sit similique ut minima ut. + example: Quia consectetur non nulla dolore quibusdam ducimus. developer_token: type: string description: Google Ads developer token - example: Voluptatum officia quod error amet quia inventore. + example: Soluta quod. refresh_token: type: string description: OAuth refresh token - example: Voluptas expedita omnis ex. + example: Iste explicabo minima commodi qui quia est. description: Google Ads OAuth credential set. Write-only; never returned. example: - client_id: Non ut voluptas et accusamus natus id. - client_secret: Et officia sequi et quam. - developer_token: Vero quisquam. - refresh_token: Quia enim illo et eligendi. + client_id: Omnis inventore voluptatum ab. + client_secret: Laboriosam quos omnis deserunt libero consequatur quisquam. + developer_token: Sapiente ex est modi cupiditate possimus ducimus. + refresh_token: At fugiat. required: - refresh_token - client_id @@ -3676,15 +5425,15 @@ components: account_id: type: string description: Provider account identifier - example: Delectus voluptatem iste nisi in. + example: Sint suscipit. brand_kit: type: string description: Per-project brand kit selector - example: Nostrum corporis quasi magnam. + example: Quo veritatis nisi velit distinctio animi. etag: type: string description: ETag header value (mirrors version) - example: Dolor occaecati earum iusto rerum sit cupiditate. + example: Non neque optio. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3692,31 +5441,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Nam quia aperiam sunt est. + example: Dolore voluptatem qui eveniet amet. label: type: string description: Optional friendly name - example: Natus expedita aspernatur consequuntur quod ducimus molestias. + example: Voluptate et. portal_id: type: string description: HubSpot portal/account ID - example: Iste qui rerum est sed sed similique. + example: Architecto molestiae cum. project_id: type: string description: Owning project - example: Libero ut est rerum est occaecati et. + example: Qui pariatur odio reprehenderit nam. sender_email: type: string description: Default sender address - example: Ea maiores quidem molestiae deleniti omnis. + example: Perspiciatis consequatur beatae sunt officiis sequi qui. sender_name: type: string description: Default sender name - example: Expedita voluptatum commodi fuga ipsa. + example: Sapiente perspiciatis dolores dignissimos soluta non vel. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -3725,21 +5474,21 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3681253484013323435 + example: 6078188127403523196 format: int64 example: - account_id: Neque facilis est qui possimus eligendi. - brand_kit: Voluptas eius error autem est est distinctio. - etag: Et nobis voluptas dignissimos exercitationem illum. + account_id: Dolor molestiae quia voluptatem deserunt libero. + brand_kit: Molestiae soluta vel ad et in voluptatem. + etag: Aut impedit a veritatis. has_credentials: false - id: Qui quidem exercitationem. - label: Sed numquam necessitatibus laudantium est ut. - portal_id: Quisquam ex est facilis qui ducimus. - project_id: Ipsa quo vel architecto quas eligendi impedit. - sender_email: Ut culpa excepturi. - sender_name: Officiis quia quisquam corrupti. - status: inactive - version: 8080806383587730069 + id: Provident rerum et voluptas tenetur esse. + label: Quaerat ipsa voluptatem. + portal_id: Consequuntur sed ea sunt debitis. + project_id: Assumenda sed minima nihil. + sender_email: Voluptas qui voluptas veniam sint voluptas. + sender_name: Non illo ab in voluptatem minus est. + status: active + version: 1552956989772584247 required: - id - project_id @@ -3754,11 +5503,11 @@ components: account_id: type: string description: Provider account identifier - example: Magni omnis. + example: Eius quo. brand_kit: type: string description: Per-project brand kit selector - example: Perferendis culpa voluptatem nostrum eaque deleniti. + example: Dolorem impedit sint consequatur. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3766,31 +5515,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Esse voluptate. + example: Iusto in commodi velit in odio. label: type: string description: Optional friendly name - example: Voluptatum et impedit quod. + example: Non architecto. portal_id: type: string description: HubSpot portal/account ID - example: Est repellat. + example: Et corrupti magnam deleniti nam laboriosam esse. project_id: type: string description: Owning project - example: Consectetur et culpa quaerat vel. + example: Aliquam voluptatem sunt et eveniet. sender_email: type: string description: Default sender address - example: Esse sit id incidunt mollitia non sed. + example: Eaque ut voluptatibus qui qui officia. sender_name: type: string description: Default sender name - example: Minima est distinctio accusamus. + example: Alias quis voluptates nihil odio facere magnam. status: type: string description: Connection status - example: inactive + example: active enum: - active - inactive @@ -3799,20 +5548,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7333781901734102627 + example: 7096858184495555885 format: int64 example: - account_id: Aliquam animi. - brand_kit: Eos iusto fugit. - has_credentials: true - id: Quo nostrum quia. - label: Velit quis. - portal_id: Error dolor placeat et. - project_id: Ut nobis. - sender_email: Et esse fugiat aliquid aspernatur voluptatem in. - sender_name: Consequatur et dolorem. - status: error - version: 2888074467672743464 + account_id: Officia suscipit qui molestias aliquid dolorum consequatur. + brand_kit: Hic ea et. + has_credentials: false + id: Nihil labore accusamus illum in delectus magnam. + label: Porro voluptatum nemo adipisci. + portal_id: Enim aut. + project_id: Velit sed laboriosam placeat nesciunt. + sender_email: Dolores sed. + sender_name: Optio enim repellendus commodi quia quod sit. + status: inactive + version: 3587423604677944667 required: - id - project_id @@ -3826,43 +5575,43 @@ components: account_id: type: string description: Provider account identifier - example: Voluptas omnis quidem dolor ipsum voluptas sed. + example: Eligendi corporis tenetur delectus excepturi et. brand_kit: type: string description: Per-project brand kit selector - example: Tempora dolor quis totam hic quibusdam eveniet. + example: Ut est est mollitia. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Asperiores quidem. + example: Aperiam impedit omnis explicabo. label: type: string description: Optional friendly name - example: Nesciunt adipisci odit corrupti laudantium consequatur hic. + example: Enim temporibus facere. portal_id: type: string description: HubSpot portal/account ID - example: Dolorem necessitatibus neque consectetur occaecati earum. + example: Id libero sunt. project_id: type: string description: Owning project - example: Est nostrum atque. + example: Et et commodi autem. sender_email: type: string description: Default sender address - example: Ut sint non eum expedita non. + example: Suscipit consequatur iure dolor sit quis ut. sender_name: type: string description: Default sender name - example: Sit voluptatem ad ab. + example: Natus enim non. status: type: string description: Connection status - example: deleted + example: inactive enum: - active - inactive @@ -3871,20 +5620,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3980984224261423037 + example: 2722095064844656519 format: int64 example: - account_id: Quidem omnis totam eos non molestias. - brand_kit: Ducimus id sint eius aut. - has_credentials: false - id: Doloribus voluptas mollitia doloremque totam. - label: Quia doloremque commodi rerum. - portal_id: Recusandae illum accusamus debitis. - project_id: Veniam et ducimus quos amet. - sender_email: Praesentium sint laudantium error. - sender_name: Nihil iure officiis nemo quia. - status: inactive - version: 6150421422111226538 + account_id: Doloribus similique et. + brand_kit: Voluptas earum provident qui. + has_credentials: true + id: Corporis omnis. + label: Delectus nisi minima quas voluptatem consequuntur. + portal_id: Aliquam dolorem voluptas autem enim aut ut. + project_id: Ea in sunt odit aspernatur. + sender_email: Eum ut rem illum. + sender_name: Autem ratione est exercitationem sapiente a animi. + status: error + version: 6167429858997341484 required: - id - project_id @@ -3898,11 +5647,11 @@ components: account_id: type: string description: Provider account identifier - example: Ab quo ullam ad. + example: Quam aliquid reiciendis maiores similique ab. brand_kit: type: string description: Per-project brand kit selector - example: Aperiam facere voluptas quis nihil. + example: Iure consectetur libero. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3910,31 +5659,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Et quia ex et alias ea. + example: Facere dolorum rem laborum dolor provident. label: type: string description: Optional friendly name - example: Laborum maiores est ea ducimus ipsum dolorem. + example: Vitae voluptas fuga aut minima inventore tenetur. portal_id: type: string description: HubSpot portal/account ID - example: Placeat explicabo. + example: Accusamus optio consequatur excepturi rerum. project_id: type: string description: Owning project - example: Quas quis corporis. + example: Odio aut suscipit. sender_email: type: string description: Default sender address - example: Rerum tenetur ut voluptatem quo. + example: In officiis dolores delectus architecto. sender_name: type: string description: Default sender name - example: Iste aut cum voluptatem qui et. + example: Velit eligendi omnis est vel est et. status: type: string description: Connection status - example: inactive + example: deleted enum: - active - inactive @@ -3943,20 +5692,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3448886829732168782 + example: 7137337173165660443 format: int64 example: - account_id: Vero ut et neque soluta distinctio voluptas. - brand_kit: Voluptatem incidunt cum. - has_credentials: false - id: Rem asperiores eligendi vero. - label: Facere quas qui aut rerum. - portal_id: Fugit eum. - project_id: Voluptatem fugiat et. - sender_email: Necessitatibus voluptatem incidunt et totam esse rerum. - sender_name: Dolorem delectus. + account_id: Ratione architecto corporis totam et. + brand_kit: Tempora reprehenderit eveniet. + has_credentials: true + id: Accusamus aut deleniti libero veritatis cum. + label: Quidem perferendis. + portal_id: Tempora magnam ea repellat qui. + project_id: Deleniti dolorem voluptas harum soluta repudiandae quisquam. + sender_email: Eveniet dolor necessitatibus est mollitia. + sender_name: Officiis dolorem necessitatibus blanditiis rerum. status: active - version: 5717670313645740058 + version: 6334382601164890326 required: - id - project_id @@ -3970,34 +5719,34 @@ components: account_id: type: string description: HubSpot list/audience ID - example: Quis quis reprehenderit non quae omnis qui. + example: Totam et voluptates atque consequatur consequuntur voluptates. brand_kit: type: string description: Per-project brand kit selector - example: Voluptatibus deserunt. + example: Adipisci minus et vero. label: type: string description: Optional friendly name - example: At odio incidunt distinctio sint qui. + example: Commodi facere vel. portal_id: type: string description: HubSpot portal/account ID - example: Cumque saepe sint quidem odio ab. + example: Quos provident. sender_email: type: string description: Default sender address - example: Aut est. + example: Et fugiat reprehenderit officiis modi quae consequatur. sender_name: type: string description: Default sender name - example: Error pariatur consequatur esse. + example: Et culpa culpa veritatis. example: - account_id: Et eligendi et numquam dolorum consequatur in. - brand_kit: Non sequi error assumenda quam deleniti et. - label: Necessitatibus eos. - portal_id: Et eum est molestiae necessitatibus magni. - sender_email: Molestiae praesentium perferendis deserunt ipsum dolorum. - sender_name: Error nulla fuga fugit repellendus et accusamus. + account_id: Ea et quidem quibusdam officiis dolor. + brand_kit: Aut mollitia pariatur perferendis placeat dolores. + label: Ea perspiciatis rerum occaecati incidunt deleniti sunt. + portal_id: Nisi sunt. + sender_email: Sint dolor eaque culpa. + sender_name: Eius omnis id itaque. required: - account_id HubspotCredentials: @@ -4006,10 +5755,10 @@ components: private_app_token: type: string description: HubSpot private app token - example: Dolor officia eligendi. + example: Molestiae veniam exercitationem blanditiis ratione non. description: HubSpot private app token. Write-only; never returned. example: - private_app_token: Provident dicta temporibus debitis. + private_app_token: Sed dolorum cupiditate quia qui dicta. required: - private_app_token InternalServerError: @@ -4029,17 +5778,105 @@ components: required: - code - message + JobCreateResponse: + type: object + properties: + job_id: + type: string + description: Poll GET /projects/{projectId}/jobs/{jobId} + example: Consequatur ut. + platforms: + type: array + items: + type: string + example: Commodi qui mollitia et eum veniam. + description: Platforms this job will create on + example: + - Eaque voluptatem omnis architecto cupiditate. + - Sint culpa sed minima. + status: + type: string + description: Initial status (always 'queued' on create) + example: queued + enum: + - queued + example: + job_id: Quos enim. + platforms: + - Molestiae ad voluptatum ducimus accusantium et. + - Quaerat et eius culpa magni illum consequatur. + - Soluta dolore vitae. + status: queued + required: + - job_id + - status + - platforms + JobPollResponse: + type: object + properties: + error: + type: string + description: Terminal error, if any + example: Sequi aut veniam ratione. + job_id: + type: string + description: Job UUID + example: Quia magnam culpa ipsam totam nulla. + result: + type: array + items: + $ref: '#/components/schemas/PlatformResult' + description: Per-platform results, written once when the job reaches a terminal state + example: + - campaign_id: Nemo perspiciatis et qui aliquam doloremque itaque. + error: Magni autem. + ok: false + platform: Ut totam ut. + - campaign_id: Nemo perspiciatis et qui aliquam doloremque itaque. + error: Magni autem. + ok: false + platform: Ut totam ut. + - campaign_id: Nemo perspiciatis et qui aliquam doloremque itaque. + error: Magni autem. + ok: false + platform: Ut totam ut. + status: + type: string + description: Job status + example: running + enum: + - queued + - running + - succeeded + - partial + - failed + example: + error: Mollitia quidem quo et enim. + job_id: Perferendis omnis ad voluptas esse. + result: + - campaign_id: Nemo perspiciatis et qui aliquam doloremque itaque. + error: Magni autem. + ok: false + platform: Ut totam ut. + - campaign_id: Nemo perspiciatis et qui aliquam doloremque itaque. + error: Magni autem. + ok: false + platform: Ut totam ut. + status: failed + required: + - job_id + - status LinkedinAdsConnection: type: object properties: account_id: type: string description: Provider account identifier - example: Cumque molestiae eos rem aut. + example: Nobis qui sunt laboriosam ipsa. etag: type: string description: ETag header value (mirrors version) - example: Non adipisci rerum. + example: Aut inventore consequatur. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4047,23 +5884,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Adipisci quaerat est nobis. + example: Rerum rem. label: type: string description: Optional friendly name - example: Et veritatis quisquam sed ratione et. + example: In vel. org_id: type: string description: LinkedIn organization URN id - example: Placeat ea odio ea eos. + example: Autem eaque eaque dicta ut magni. project_id: type: string description: Owning project - example: Fugit non fuga rem adipisci. + example: Vitae qui explicabo tempora accusantium aut. status: type: string description: Connection status - example: active + example: deleted enum: - active - inactive @@ -4072,18 +5909,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7868855978793741277 + example: 3265309804259735983 format: int64 example: - account_id: Nobis itaque est enim magnam pariatur. - etag: Praesentium ipsam officiis cum ipsum recusandae. + account_id: Alias quod minima blanditiis. + etag: Iure dolores dolores dolorem sit nihil. has_credentials: true - id: Ipsum et delectus dolorum eveniet. - label: Maxime maxime porro enim. - org_id: Adipisci ullam pariatur ex voluptas. - project_id: Alias ea quibusdam. - status: active - version: 5465298508990820984 + id: Error deleniti vitae molestiae qui nobis. + label: Quia enim non asperiores velit. + org_id: Sit est atque qui quaerat qui vitae. + project_id: Aut temporibus dolorem cumque quis. + status: inactive + version: 4548596825330631938 required: - id - project_id @@ -4098,31 +5935,31 @@ components: account_id: type: string description: Provider account identifier - example: Dolorum deserunt. + example: Et laboriosam veritatis saepe numquam. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Rerum adipisci. + example: Quas delectus a maxime consequatur iste eum. label: type: string description: Optional friendly name - example: Non laudantium sunt minima repudiandae illo harum. + example: Et at iusto optio. org_id: type: string description: LinkedIn organization URN id - example: Eos nihil ipsam ex odio totam. + example: Qui quisquam quae. project_id: type: string description: Owning project - example: Enim accusantium labore esse numquam. + example: Vel impedit et unde et rerum. status: type: string description: Connection status - example: deleted + example: error enum: - active - inactive @@ -4131,17 +5968,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2440284349670140496 + example: 1129434020019069718 format: int64 example: - account_id: Omnis consequatur. + account_id: Ea nostrum corrupti ipsum minima consequuntur. has_credentials: true - id: Deleniti placeat fuga aut saepe. - label: Ut quis ut accusantium. - org_id: Aut autem sint minus voluptatum. - project_id: Reiciendis pariatur repellat. - status: error - version: 4719926195195381744 + id: Magni temporibus et molestias. + label: Iste ullam sunt rem non doloribus omnis. + org_id: Hic et illo aut natus et accusamus. + project_id: Molestias et officiis asperiores quibusdam accusamus. + status: active + version: 8161899588231294433 required: - id - project_id @@ -4155,7 +5992,7 @@ components: account_id: type: string description: Provider account identifier - example: Quo veritatis quis delectus est. + example: Eos quidem debitis iusto molestias itaque consequatur. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4163,23 +6000,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Tempore quae. + example: Qui et dolorem. label: type: string description: Optional friendly name - example: Dolores tenetur ut illum. + example: Expedita error cumque. org_id: type: string description: LinkedIn organization URN id - example: Expedita accusantium vel voluptate. + example: Fugit et soluta. project_id: type: string description: Owning project - example: Consequatur dolore unde illum. + example: Laboriosam repellat harum expedita aut quidem beatae. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -4188,17 +6025,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1676862666948614995 + example: 7037294126068689767 format: int64 example: - account_id: Magni consequatur dignissimos itaque quia. + account_id: Iure ut. has_credentials: true - id: Repellat assumenda nesciunt distinctio eaque aliquam. - label: Et reprehenderit dignissimos. - org_id: Et voluptatem deserunt consequuntur omnis sed dolore. - project_id: Veniam consectetur deserunt in hic dolorum facere. - status: inactive - version: 7072791787806691868 + id: Et qui officiis repudiandae qui iste accusantium. + label: Consequatur provident ea sint. + org_id: Eum quibusdam et minus id deleniti placeat. + project_id: Voluptatem iste nemo numquam unde perspiciatis. + status: active + version: 3173101195127892589 required: - id - project_id @@ -4212,7 +6049,7 @@ components: account_id: type: string description: Provider account identifier - example: Assumenda dignissimos beatae vitae aut ea dolor. + example: Autem quia velit aliquid quae. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4220,23 +6057,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Voluptatibus quod necessitatibus non iste ad rem. + example: Quia et non id sunt adipisci consectetur. label: type: string description: Optional friendly name - example: Harum corporis deleniti doloribus. + example: Soluta nostrum cumque explicabo pariatur voluptatum molestias. org_id: type: string description: LinkedIn organization URN id - example: Sit architecto ut unde. + example: Consequatur vero possimus. project_id: type: string description: Owning project - example: Sunt qui cupiditate ullam ab suscipit et. + example: Modi quo et inventore eum autem quia. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -4245,17 +6082,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6055913800302101668 + example: 700201239225519995 format: int64 example: - account_id: Praesentium tenetur quas at ut necessitatibus dolor. + account_id: Sequi dolor eos fugiat rem. has_credentials: false - id: Sapiente possimus. - label: Eaque voluptate reprehenderit sed voluptatem sed illo. - org_id: Nihil id unde veritatis officiis aut fugiat. - project_id: Voluptates atque. - status: error - version: 446959638978499075 + id: Maiores voluptas. + label: Qui facilis possimus. + org_id: Pariatur et et deserunt facere ea incidunt. + project_id: Corporis quam quos dolor id debitis eos. + status: inactive + version: 1986183550074348775 required: - id - project_id @@ -4273,14 +6110,14 @@ components: label: type: string description: Optional friendly name - example: Cumque totam nulla aut reprehenderit. + example: Officia dolores et reprehenderit. org_id: type: string description: LinkedIn organization URN id example: "208777" example: account_id: "538170226" - label: Illum rerum error incidunt. + label: Ex dolor. org_id: "208777" required: - account_id @@ -4291,10 +6128,10 @@ components: access_token: type: string description: OAuth access token - example: Aspernatur possimus. + example: Voluptate est reprehenderit consectetur accusamus eaque inventore. description: LinkedIn Ads credential. Write-only; never returned. example: - access_token: Repellendus quo quidem. + access_token: Aut explicabo. required: - access_token MetaAdsConnection: @@ -4303,15 +6140,15 @@ components: account_id: type: string description: Provider account identifier - example: Iusto ad animi eos doloribus autem corrupti. + example: Libero officia et quia. app_id: type: string description: Meta app ID - example: Laborum eum et quidem. + example: Necessitatibus eos rerum. etag: type: string description: ETag header value (mirrors version) - example: Suscipit quas sunt eos nulla. + example: Qui maxime eos deserunt. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4319,23 +6156,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Facere exercitationem et exercitationem. + example: Est consequatur rerum neque velit nulla modi. label: type: string description: Optional friendly name - example: Rerum quidem accusamus et provident provident omnis. + example: Nihil at. page_id: type: string description: Facebook page ID - example: Perferendis qui maiores aut reiciendis. + example: Expedita sed velit distinctio. project_id: type: string description: Owning project - example: Eaque deleniti. + example: Optio quia similique vero est impedit quo. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -4344,19 +6181,19 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1750381436080099913 + example: 4061552416134608654 format: int64 example: - account_id: Ad non. - app_id: Est voluptas sequi. - etag: Possimus consequatur est unde. - has_credentials: false - id: Quasi quis ab deserunt quia magnam. - label: Quod modi voluptas dolores commodi dicta. - page_id: Et laudantium. - project_id: Minus et aut consequatur non cumque. - status: active - version: 3589524561643771783 + account_id: Vero aut repellendus reiciendis. + app_id: Ea ad ducimus consequatur aut. + etag: Atque et. + has_credentials: true + id: Veniam amet et quisquam voluptatem fuga. + label: Optio rerum vel molestiae id. + page_id: Quia iure aut tenetur. + project_id: Qui quas aut provident suscipit. + status: inactive + version: 8332327938373479794 required: - id - project_id @@ -4371,35 +6208,35 @@ components: account_id: type: string description: Provider account identifier - example: Nostrum tempore. + example: Non impedit incidunt. app_id: type: string description: Meta app ID - example: Rerum explicabo. + example: Alias laudantium sit et. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Ut commodi et voluptas tenetur nobis facere. + example: Molestiae modi. label: type: string description: Optional friendly name - example: Blanditiis mollitia dolores illo nostrum magnam. + example: Temporibus ullam sint aliquam ea. page_id: type: string description: Facebook page ID - example: Deserunt repellendus corrupti itaque quo sit. + example: Rerum corrupti. project_id: type: string description: Owning project - example: Quo corporis recusandae dicta quibusdam. + example: Libero consectetur non. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -4408,18 +6245,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6534648276901202778 + example: 1431900350448492398 format: int64 example: - account_id: Perspiciatis porro. - app_id: Deserunt quod. + account_id: Amet et vel ipsa ad doloremque qui. + app_id: Eum voluptatem debitis aliquid accusamus eligendi itaque. has_credentials: false - id: Eum et ipsum expedita reprehenderit eos. - label: Aperiam et nihil. - page_id: Non rem error. - project_id: Dolorum ut architecto deleniti nostrum. - status: deleted - version: 2783740423386270532 + id: Sed eius reiciendis rerum voluptatem suscipit rerum. + label: Voluptas voluptatem eum consequuntur. + page_id: Est quidem in. + project_id: Et ex aut cupiditate qui placeat omnis. + status: inactive + version: 2895836647375905460 required: - id - project_id @@ -4433,35 +6270,35 @@ components: account_id: type: string description: Provider account identifier - example: Voluptate necessitatibus enim et et itaque. + example: Nam rerum omnis praesentium omnis amet quia. app_id: type: string description: Meta app ID - example: Nihil aut rerum est vel. + example: At explicabo reiciendis. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Aperiam quos perferendis nisi blanditiis eveniet. + example: Est temporibus explicabo doloremque possimus. label: type: string description: Optional friendly name - example: Nobis omnis. + example: Autem repellat et. page_id: type: string description: Facebook page ID - example: Rerum quis et modi optio labore omnis. + example: Officia rerum eligendi ex. project_id: type: string description: Owning project - example: Ea enim ut corporis dolorum consequatur eius. + example: Cupiditate ab omnis nesciunt consequatur commodi. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -4470,18 +6307,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 9014435567821091487 + example: 5777051591540907893 format: int64 example: - account_id: Dolor aut assumenda fugiat. - app_id: Aut quas suscipit eos. + account_id: Voluptates voluptatem similique aut. + app_id: Natus expedita est sed. has_credentials: true - id: Rem ab corporis. - label: Porro et. - page_id: Inventore alias error aut maxime dicta. - project_id: Non dolorem. - status: deleted - version: 5862072609653585869 + id: At ratione unde dicta. + label: Voluptas perferendis et perferendis. + page_id: Voluptatem sit excepturi eaque. + project_id: Doloribus aspernatur quod ipsum pariatur explicabo nesciunt. + status: error + version: 4610591738843411818 required: - id - project_id @@ -4495,31 +6332,31 @@ components: account_id: type: string description: Provider account identifier - example: Dolor et corrupti voluptas neque vitae. + example: Repudiandae hic nostrum qui sed est architecto. app_id: type: string description: Meta app ID - example: Rerum numquam amet deserunt rem quia. + example: Eveniet magnam dolor sint. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Ipsa rerum velit et impedit. + example: Ducimus qui quas neque eum voluptatem. label: type: string description: Optional friendly name - example: Velit quam omnis. + example: Consectetur consequatur autem eveniet magnam dolor. page_id: type: string description: Facebook page ID - example: Mollitia est assumenda aut harum. + example: Id doloremque ad dolores tempore ut est. project_id: type: string description: Owning project - example: Quaerat labore dolor. + example: Velit corporis nemo. status: type: string description: Connection status @@ -4532,18 +6369,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1514261665871912761 + example: 3642485435905489461 format: int64 example: - account_id: Deleniti deserunt labore. - app_id: Molestiae deserunt voluptates. - has_credentials: true - id: Ipsam dolores et et qui ut. - label: Velit esse voluptate eum laboriosam distinctio. - page_id: Adipisci quo. - project_id: Numquam omnis ullam facilis ab sapiente. - status: error - version: 3954861542185537606 + account_id: Doloremque pariatur qui magni facere. + app_id: Ut voluptas est est accusamus. + has_credentials: false + id: Ipsa nam ipsa magnam praesentium eos cum. + label: Illum natus doloremque facere quis et beatae. + page_id: Sit animi dolor ex saepe ipsa. + project_id: Sed est recusandae enim quidem. + status: active + version: 1460268576636670658 required: - id - project_id @@ -4561,20 +6398,20 @@ components: app_id: type: string description: Meta app ID - example: Eius sed qui deserunt perspiciatis nihil. + example: Adipisci ut. label: type: string description: Optional friendly name - example: Et eveniet. + example: Excepturi natus est est saepe doloribus unde. page_id: type: string description: Facebook page ID - example: Corporis ut non. + example: Aut perspiciatis. example: account_id: act_193556282970417 - app_id: Aut hic. - label: Mollitia tempora minima voluptas. - page_id: Facere aut facilis porro dolor ipsa. + app_id: Dolores earum. + label: Sit totam ut et. + page_id: Quibusdam ea nam aut. required: - account_id MetaAdsCredentials: @@ -4583,15 +6420,15 @@ components: access_token: type: string description: Meta access token - example: Aperiam voluptates. + example: Optio non. app_secret: type: string description: Meta app secret - example: Vero ducimus. + example: Optio in ratione accusamus quasi. description: Meta Ads credential. Write-only; never returned. example: - access_token: Recusandae aut perferendis. - app_secret: Est eaque ipsam harum nisi nobis. + access_token: Ipsam eligendi est dolore. + app_secret: Consequatur aut vero. required: - access_token - app_secret @@ -4601,35 +6438,35 @@ components: account_id: type: string description: Provider account identifier - example: Et nulla officia vitae. + example: Ut itaque voluptatem unde adipisci aut. customer_id: type: string description: Microsoft Advertising customer ID - example: Et sunt quis dicta voluptate aut in. + example: Non laudantium quis ab non sunt. etag: type: string description: ETag header value (mirrors version) - example: Ut asperiores quo aut. + example: Aut praesentium eos ex laborum et doloremque. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Perspiciatis id. + example: Et natus possimus cupiditate. label: type: string description: Optional friendly name - example: Nihil nihil quae porro qui rem autem. + example: Repudiandae eum. project_id: type: string description: Owning project - example: Ut non error aut et fuga. + example: Molestiae quia neque atque delectus. status: type: string description: Connection status - example: inactive + example: deleted enum: - active - inactive @@ -4638,18 +6475,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2431299518347433245 + example: 9192676630235860462 format: int64 example: - account_id: Id temporibus et provident maiores neque quis. - customer_id: Sunt assumenda. - etag: Eligendi ut. - has_credentials: false - id: Provident nobis soluta culpa vitae. - label: Laudantium amet veritatis rerum itaque consequatur laboriosam. - project_id: Excepturi deleniti voluptas aperiam vel. + account_id: Voluptatem velit odio temporibus aut. + customer_id: Enim velit voluptates officiis tempora possimus rerum. + etag: Vitae dolor soluta beatae. + has_credentials: true + id: Quo est. + label: Quas quia quam voluptate nam iste. + project_id: Beatae totam. status: error - version: 4162198444433663334 + version: 1007882038292754957 required: - id - project_id @@ -4664,31 +6501,31 @@ components: account_id: type: string description: Provider account identifier - example: Earum minus autem omnis qui debitis eum. + example: Quisquam sit ut aliquid consectetur. customer_id: type: string description: Microsoft Advertising customer ID - example: Quidem necessitatibus voluptatem consequatur sit. + example: Quia assumenda aut. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Esse officia explicabo dolor deserunt aspernatur. + example: Nobis ipsam blanditiis commodi accusamus maxime. label: type: string description: Optional friendly name - example: Cupiditate mollitia hic vel ut. + example: Iusto aspernatur fugit et facere eligendi. project_id: type: string description: Owning project - example: Aspernatur illo accusamus. + example: Quia sed eum ad sunt impedit sint. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -4697,17 +6534,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3699306814781450025 + example: 8275500991568401541 format: int64 example: - account_id: Sed corrupti sit. - customer_id: Consectetur tenetur eum. + account_id: Id voluptatem et et et eveniet. + customer_id: Ex dolor quia et dolores. has_credentials: true - id: Et vel beatae molestiae fuga. - label: Aut ut. - project_id: Vitae consequatur eos. - status: active - version: 436879921687162189 + id: Voluptate quam delectus optio. + label: Molestiae fuga. + project_id: Ducimus aliquam vel cum alias. + status: error + version: 2847235492838917454 required: - id - project_id @@ -4721,31 +6558,31 @@ components: account_id: type: string description: Provider account identifier - example: Qui impedit temporibus eveniet eos omnis cumque. + example: Ipsum est a. customer_id: type: string description: Microsoft Advertising customer ID - example: Natus et unde. + example: Molestiae similique optio et corporis. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Aut ut sit nisi architecto. + example: Distinctio ea. label: type: string description: Optional friendly name - example: Praesentium voluptate. + example: Velit architecto error consequatur nam. project_id: type: string description: Owning project - example: Rerum et recusandae possimus quis. + example: Ab iste earum consequatur laboriosam maxime nemo. status: type: string description: Connection status - example: inactive + example: active enum: - active - inactive @@ -4754,17 +6591,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1288874109548033633 + example: 6583480909021941947 format: int64 example: - account_id: Hic est quia pariatur odit ullam. - customer_id: Sed quo perferendis nemo ut quisquam. - has_credentials: true - id: Explicabo repudiandae aut reiciendis recusandae molestias. - label: Quod impedit accusamus incidunt minus veritatis enim. - project_id: Vel inventore aliquid aut non eligendi accusamus. - status: error - version: 2175885598856983154 + account_id: Ex enim rem. + customer_id: Quo sit iure quos aut. + has_credentials: false + id: Eligendi voluptatem repellat consectetur alias reiciendis voluptas. + label: Dolorum enim doloribus. + project_id: Cumque est quis nostrum quod. + status: active + version: 2799898264679301316 required: - id - project_id @@ -4778,11 +6615,11 @@ components: account_id: type: string description: Provider account identifier - example: Hic rerum accusamus velit error. + example: Repellendus et neque deserunt eligendi rerum architecto. customer_id: type: string description: Microsoft Advertising customer ID - example: Dolorem deserunt quasi in aut nam rerum. + example: Sapiente dolor unde harum dolorem iusto quia. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4790,19 +6627,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Repellendus quos magnam. + example: Facere debitis quo. label: type: string description: Optional friendly name - example: Pariatur repellat vel. + example: Modi eveniet. project_id: type: string description: Owning project - example: In id voluptatem nihil quaerat. + example: Fuga quaerat sed. status: type: string description: Connection status - example: deleted + example: error enum: - active - inactive @@ -4811,17 +6648,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 8411140861799189617 + example: 402075575675880099 format: int64 example: - account_id: Sint est sed. - customer_id: Illum quisquam ex ipsa. + account_id: Ratione ut laboriosam dolorem ut dolorem odio. + customer_id: Inventore iste nemo rerum corporis repellat nemo. has_credentials: true - id: Distinctio magnam. - label: Quia magni aut nisi est. - project_id: Architecto deserunt vero ducimus distinctio. - status: active - version: 6452524533918462281 + id: Eos doloremque qui repellendus. + label: Ea et nesciunt et culpa similique. + project_id: Incidunt in. + status: deleted + version: 2480470963761289215 required: - id - project_id @@ -4835,19 +6672,19 @@ components: account_id: type: string description: Microsoft Advertising account ID - example: Ex quo praesentium voluptas. + example: Rerum maiores in alias est laboriosam saepe. customer_id: type: string description: Microsoft Advertising customer ID - example: Est officia et. + example: Perferendis placeat earum occaecati et. label: type: string description: Optional friendly name - example: Voluptatem mollitia omnis doloribus qui consequatur et. + example: Unde perspiciatis. example: - account_id: Eos et architecto voluptas. - customer_id: Vel natus provident. - label: Numquam accusamus. + account_id: Assumenda quam possimus aut ipsa. + customer_id: Quae et perspiciatis numquam. + label: Eaque magni omnis dolorem. required: - account_id MicrosoftAdsCredentials: @@ -4856,25 +6693,25 @@ components: client_id: type: string description: OAuth client id - example: Architecto itaque doloribus voluptatem consequatur quis fugiat. + example: Accusantium est. client_secret: type: string description: OAuth client secret - example: Recusandae qui culpa. + example: Molestias reprehenderit. developer_token: type: string description: Microsoft Advertising developer token - example: Sed rerum dicta distinctio voluptas ut ut. + example: Dolorem similique sapiente. refresh_token: type: string description: OAuth refresh token - example: Accusamus et atque enim. + example: Pariatur esse. description: Microsoft Advertising OAuth credential set. Write-only; never returned. example: - client_id: Fuga nostrum velit hic excepturi omnis. - client_secret: Distinctio quos ut unde et atque. - developer_token: In recusandae maiores et voluptatem. - refresh_token: Odio labore pariatur nobis. + client_id: Ea pariatur. + client_secret: Aut rem molestiae assumenda ea. + developer_token: Sed qui dolor illo facere ut voluptatem. + refresh_token: Nam repellat. required: - client_id - client_secret @@ -4897,6 +6734,33 @@ components: required: - code - message + PlatformResult: + type: object + properties: + campaign_id: + type: string + description: Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost. + example: Laudantium dolor ipsam recusandae maxime. + error: + type: string + description: Failure reason (present when not ok) + example: Repellendus assumenda voluptatem aut debitis. + ok: + type: boolean + description: Whether the campaign was created (or reused) successfully + example: false + platform: + type: string + description: Platform this result is for + example: In commodi et asperiores et. + example: + campaign_id: Et consequatur et et itaque qui repudiandae. + error: Atque ullam qui rerum numquam quo omnis. + ok: true + platform: Unde velit eos eius accusantium odit rerum. + required: + - platform + - ok PreconditionFailedError: type: object properties: @@ -4937,31 +6801,31 @@ components: account_id: type: string description: Provider account identifier - example: Dolores assumenda accusantium mollitia fugiat dolore similique. + example: Ut quas officiis mollitia ducimus sunt repellendus. etag: type: string description: ETag header value (mirrors version) - example: Molestiae quos cum. + example: Aliquam est autem vel eos minima. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Cum molestias fuga voluptatibus quis. + example: Odit illo ut ut. label: type: string description: Optional friendly name - example: Nobis aliquid rerum consequatur officiis velit. + example: Debitis atque laboriosam unde facilis. project_id: type: string description: Owning project - example: Aut rerum ut blanditiis dolor. + example: Nam modi nostrum assumenda alias sed. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -4970,17 +6834,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 4135215091203906464 + example: 2463177480955435124 format: int64 example: - account_id: Natus placeat esse est in deserunt aliquid. - etag: Sed et odio sunt totam incidunt. + account_id: Facilis dolor consectetur sed. + etag: Et itaque. has_credentials: false - id: Architecto doloremque aperiam doloribus aut qui. - label: Distinctio ipsum ab. - project_id: Placeat cumque iure veritatis cumque nisi. - status: error - version: 6763438623066254599 + id: Quas est deserunt maiores consequatur eum. + label: Occaecati hic ut magnam facilis. + project_id: Voluptatum pariatur non ratione quo ullam. + status: inactive + version: 3072744263422347458 required: - id - project_id @@ -4995,7 +6859,7 @@ components: account_id: type: string description: Provider account identifier - example: Assumenda harum eos eveniet ut et laboriosam. + example: Est quia ducimus inventore. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5003,19 +6867,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Culpa saepe necessitatibus. + example: Nisi consequatur non quam suscipit ea ea. label: type: string description: Optional friendly name - example: Voluptas aperiam. + example: Autem aut maiores ut sint. project_id: type: string description: Owning project - example: Dolores enim. + example: Quia sed harum. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -5024,16 +6888,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6456486653687697700 + example: 4610200042662121887 format: int64 example: - account_id: Cumque quae. - has_credentials: false - id: Qui eaque ut laudantium. - label: Laborum aspernatur voluptate itaque fugit. - project_id: Ratione id. - status: deleted - version: 5238309974693293664 + account_id: Impedit perspiciatis ipsam sunt distinctio consequuntur. + has_credentials: true + id: Et mollitia exercitationem cumque. + label: Eaque omnis nihil itaque. + project_id: Similique sequi occaecati. + status: error + version: 2750059486165092858 required: - id - project_id @@ -5047,7 +6911,7 @@ components: account_id: type: string description: Provider account identifier - example: Dolore velit pariatur non assumenda perspiciatis dolor. + example: Deserunt provident voluptates cumque reiciendis error minus. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5055,19 +6919,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Odit consequatur. + example: Recusandae amet. label: type: string description: Optional friendly name - example: Ut ullam. + example: Expedita nihil molestiae soluta consequuntur sapiente. project_id: type: string description: Owning project - example: Vero soluta omnis eius. + example: Magni ut amet. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -5076,16 +6940,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3837313741085790100 + example: 8154718885862958757 format: int64 example: - account_id: Animi molestiae aut fugit alias nisi qui. + account_id: Rerum nesciunt veritatis consequatur porro. has_credentials: false - id: Molestiae ea ullam minima voluptatem vel. - label: Beatae quia minus vero at ut. - project_id: Dolore repudiandae necessitatibus dolorem. - status: inactive - version: 8129543513726361746 + id: Qui voluptas culpa. + label: Delectus non aut ipsum. + project_id: Iste nihil itaque et. + status: deleted + version: 1595767401527646160 required: - id - project_id @@ -5099,27 +6963,27 @@ components: account_id: type: string description: Provider account identifier - example: Autem iste ea in ipsa. + example: Nemo aut et qui. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Deserunt dolorum nihil. + example: Ratione provident laborum. label: type: string description: Optional friendly name - example: Odio odio sed minus quia quia ea. + example: Et quisquam dolores. project_id: type: string description: Owning project - example: Doloremque explicabo ut. + example: Sed voluptatem repudiandae soluta. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -5128,16 +6992,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 4150548058954901958 + example: 8907067316289641877 format: int64 example: - account_id: Quibusdam consequuntur dolorem fugiat. + account_id: Aliquid porro deserunt nostrum sed sint voluptas. has_credentials: true - id: Perspiciatis magni tempore sunt itaque. - label: Sit beatae ut modi voluptatum veniam. - project_id: Ut ex. + id: Officiis et error nemo iste harum. + label: Culpa est ex. + project_id: Commodi occaecati. status: error - version: 5881477690177318757 + version: 2407825525433943238 required: - id - project_id @@ -5155,10 +7019,10 @@ components: label: type: string description: Optional friendly name - example: Qui molestias. + example: Ipsam aperiam et totam in. example: account_id: t2_gv9wtbfa - label: Molestiae minus dolor accusantium. + label: Numquam voluptatem maiores ipsam dicta cum odit. required: - account_id RedditAdsCredentials: @@ -5167,20 +7031,20 @@ components: client_id: type: string description: OAuth client id - example: Numquam quidem qui. + example: Cumque laborum ipsum accusantium voluptatem libero. client_secret: type: string description: OAuth client secret - example: Laboriosam possimus dolor facere velit dolores. + example: Eligendi non. refresh_token: type: string description: OAuth refresh token - example: Alias ad voluptatem accusantium tempore impedit. + example: Necessitatibus non eum ipsum eveniet quam. description: Reddit Ads OAuth credential. Write-only; never returned. example: - client_id: Et expedita itaque quos. - client_secret: Quos accusantium fugit. - refresh_token: Excepturi saepe sit vel perspiciatis neque. + client_id: Aut amet aut voluptatum magnam. + client_secret: Accusamus eos neque velit. + refresh_token: In iure molestiae quisquam. required: - client_id - client_secret @@ -5209,10 +7073,10 @@ components: $ref: '#/components/schemas/GoogleAdsCredentials' example: credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Natus provident est. + client_secret: Id dolore. + developer_token: Non error. + refresh_token: Accusamus aperiam eos et architecto voluptas rerum. required: - credentials SetCredentialHubspotRequestBody: @@ -5222,7 +7086,7 @@ components: $ref: '#/components/schemas/HubspotCredentials' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Molestiae suscipit non iusto. required: - credentials SetCredentialLinkedinAdsRequestBody: @@ -5232,7 +7096,7 @@ components: $ref: '#/components/schemas/LinkedinAdsCredentials' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Eum est molestiae necessitatibus magni. required: - credentials SetCredentialMetaAdsRequestBody: @@ -5242,8 +7106,8 @@ components: $ref: '#/components/schemas/MetaAdsCredentials' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Eius est rerum illo mollitia. + app_secret: Et dolor et consectetur sint error. required: - credentials SetCredentialRedditAdsRequestBody: @@ -5253,9 +7117,9 @@ components: $ref: '#/components/schemas/RedditAdsCredentials' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magnam ut similique ea minima magnam. + client_secret: Ut quo iure. + refresh_token: Sit est dolores est et. required: - credentials SetCredentialTwitterAdsRequestBody: @@ -5265,10 +7129,10 @@ components: $ref: '#/components/schemas/TwitterAdsCredentials' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Possimus et. + access_token_secret: Deserunt consequuntur omnis. + consumer_key: Dignissimos voluptas magni consequatur dignissimos itaque. + consumer_secret: Ut aperiam. required: - credentials TwitterAdsConnection: @@ -5277,15 +7141,15 @@ components: account_id: type: string description: Provider account identifier - example: Quisquam incidunt qui. + example: Temporibus in qui provident corrupti ut similique. etag: type: string description: ETag header value (mirrors version) - example: Quia exercitationem consequatur velit. + example: Consectetur voluptas voluptas voluptatem. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Quia culpa iste id sit incidunt. + example: Quia facere vitae aperiam. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5293,15 +7157,15 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Mollitia atque atque dolores accusantium non dolor. + example: Laudantium eligendi ut et. label: type: string description: Optional friendly name - example: Nobis labore dicta. + example: Saepe beatae. project_id: type: string description: Owning project - example: Enim qui officia id necessitatibus mollitia soluta. + example: Maiores nam sequi iste iusto reiciendis ipsa. status: type: string description: Connection status @@ -5314,18 +7178,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2620236536617279883 + example: 4247878238148524808 format: int64 example: - account_id: Accusantium enim temporibus dolor ipsam. - etag: Aut ducimus dolores. - funding_instrument_id: Dolorum minus voluptatem. - has_credentials: false - id: Officiis est. - label: Tempore nemo illum ut maiores. - project_id: Qui quisquam voluptatibus sint quia. + account_id: Aut et ut dolorem tempore. + etag: Commodi sit omnis. + funding_instrument_id: Porro saepe eius earum quo porro. + has_credentials: true + id: Corrupti dicta pariatur totam quas deserunt. + label: In voluptate debitis earum voluptates repellat. + project_id: Quis impedit. status: error - version: 6092912427529457622 + version: 1517802020058879691 required: - id - project_id @@ -5340,31 +7204,31 @@ components: account_id: type: string description: Provider account identifier - example: Delectus ex aut quia. + example: Est aut et rem eos quia et. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Corporis consequatur voluptatem voluptates. + example: Facere dolorum ab laboriosam placeat atque cumque. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Soluta odit odio praesentium perferendis eveniet sint. + example: Quam fugiat adipisci non rerum et. label: type: string description: Optional friendly name - example: Temporibus quis iste eaque ratione debitis nam. + example: Quaerat laborum voluptatibus accusamus. project_id: type: string description: Owning project - example: Quam dicta voluptate consequuntur voluptatem est. + example: Et eum tenetur. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -5373,17 +7237,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 629084196159484927 + example: 942544011211036746 format: int64 example: - account_id: Velit fuga quos. - funding_instrument_id: Sed aut quia nihil est quia. + account_id: Eaque eos veniam quidem maiores adipisci. + funding_instrument_id: Molestias ullam aut odit repellat nihil at. has_credentials: true - id: Reprehenderit provident repudiandae sit. - label: Ut sunt aliquam. - project_id: Aut aut eos magnam quisquam et. - status: active - version: 2879571083312014203 + id: Alias nihil. + label: Voluptas ullam optio. + project_id: Temporibus error incidunt. + status: deleted + version: 5983859909197428373 required: - id - project_id @@ -5397,11 +7261,11 @@ components: account_id: type: string description: Provider account identifier - example: Velit laudantium error. + example: Aliquam perspiciatis libero est ratione pariatur. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Beatae harum similique corporis id eum. + example: Corrupti quam unde nemo ipsum et illum. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5409,15 +7273,15 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Fugiat est itaque. + example: Reprehenderit nemo voluptatibus. label: type: string description: Optional friendly name - example: Culpa est possimus dolores dignissimos. + example: Ipsa quis animi aliquam sunt. project_id: type: string description: Owning project - example: Fugiat aut dicta libero. + example: Perferendis corrupti mollitia tempore aut. status: type: string description: Connection status @@ -5430,17 +7294,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7144064222780250488 + example: 3919079788188515327 format: int64 example: - account_id: Enim vitae officia dolorem odio. - funding_instrument_id: Excepturi minima illo dolores. - has_credentials: false - id: Et itaque rem eos et deleniti dolores. - label: Facere qui omnis. - project_id: Facilis qui libero. - status: active - version: 6722562537039921847 + account_id: Repellendus voluptatem. + funding_instrument_id: Saepe quo quis ut in voluptatem. + has_credentials: true + id: Rerum est. + label: Minus aut ut non. + project_id: Enim est sapiente. + status: error + version: 6704106453019920248 required: - id - project_id @@ -5454,31 +7318,31 @@ components: account_id: type: string description: Provider account identifier - example: Non quo necessitatibus. + example: Qui doloremque magni est error maiores. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Laudantium nostrum earum dolores sit. + example: Est eum nemo architecto est aut. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: A sint quod sunt quae et. + example: Nesciunt magnam omnis est unde. label: type: string description: Optional friendly name - example: Ut mollitia nihil ducimus. + example: Eum animi sed explicabo et quo et. project_id: type: string description: Owning project - example: Iure quis. + example: Rerum eius vitae perferendis quod. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -5487,17 +7351,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3532519562043610511 + example: 7817365326411446744 format: int64 example: - account_id: At fugit dolor. - funding_instrument_id: Non exercitationem modi possimus consequatur non. + account_id: Ut et sit quae similique inventore. + funding_instrument_id: Illum laboriosam debitis aut vitae est est. has_credentials: false - id: Exercitationem accusantium nihil. - label: Amet omnis. - project_id: Quod alias ipsum voluptates pariatur magni. - status: error - version: 5818865715898604715 + id: Ab culpa consequatur. + label: Excepturi et eveniet sunt ut. + project_id: Veniam omnis. + status: active + version: 328362383505116272 required: - id - project_id @@ -5515,15 +7379,15 @@ components: funding_instrument_id: type: string description: Funding instrument for the ad account - example: Ut mollitia accusantium dignissimos. + example: Dolor et aliquam deleniti. label: type: string description: Optional friendly name - example: Fugit provident officia officiis. + example: Et nemo dicta illum omnis. example: account_id: 8r7gb - funding_instrument_id: Rerum ut necessitatibus illum ratione. - label: Consequatur qui molestiae velit ratione nesciunt et. + funding_instrument_id: Est dolor velit et voluptatem mollitia placeat. + label: Nemo cum delectus laudantium. required: - account_id TwitterAdsCredentials: @@ -5532,30 +7396,42 @@ components: access_token: type: string description: OAuth 1.0a access token - example: At perspiciatis quisquam perferendis. + example: Perferendis et. access_token_secret: type: string description: OAuth 1.0a access token secret - example: Ut alias quis nihil dolore suscipit reprehenderit. + example: Beatae facilis dolor quod nihil quia. consumer_key: type: string description: OAuth 1.0a consumer key - example: Id ipsa perspiciatis sunt ut velit. + example: A debitis ut ea. consumer_secret: type: string description: OAuth 1.0a consumer secret - example: Aut mollitia aut molestiae exercitationem et. + example: Quos qui quo et. description: X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned. example: - access_token: Et aut consequatur inventore enim reiciendis. - access_token_secret: Neque ut rerum facilis quia molestiae ratione. - consumer_key: Aut animi laudantium ullam. - consumer_secret: Autem et id laboriosam nihil ea. + access_token: Non consequuntur qui qui impedit assumenda voluptas. + access_token_secret: Consequuntur provident nostrum vel ut. + consumer_key: Sed voluptas fuga officia nulla. + consumer_secret: Recusandae consequatur nulla. required: - consumer_key - consumer_secret - access_token - access_token_secret + UpdateCampaignRequestBody: + type: object + properties: + campaign: + $ref: '#/components/schemas/CampaignUpdateInput' + example: + campaign: + campaign_name: Ad voluptatem. + config: Et expedita itaque quos. + status: Tempore impedit. + required: + - campaign UpdateGoogleAdsRequestBody: type: object properties: @@ -5575,12 +7451,12 @@ components: $ref: '#/components/schemas/HubspotConnectionConfig' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Voluptas aperiam. + brand_kit: Itaque fugit rerum cumque. + label: Dolores enim. + portal_id: Assumenda harum eos eveniet ut et laboriosam. + sender_email: Quos expedita voluptas qui eaque. + sender_name: Laudantium nostrum ratione id eius laborum aspernatur. required: - config UpdateLinkedinAdsRequestBody: @@ -5591,7 +7467,7 @@ components: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Dolorum consequatur in est. org_id: "208777" required: - config @@ -5603,9 +7479,9 @@ components: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Distinctio sunt aliquid harum quis nam rerum. + label: Excepturi blanditiis officiis quia quisquam. + page_id: Distinctio voluptas eius error autem est. required: - config UpdateMicrosoftAdsRequestBody: @@ -5615,9 +7491,9 @@ components: $ref: '#/components/schemas/MicrosoftAdsConnectionConfig' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Enim non rem error voluptas deserunt. + customer_id: Officiis aperiam quos. + label: Nihil dolores perspiciatis porro veniam distinctio. required: - config UpdateRedditAdsRequestBody: @@ -5628,7 +7504,7 @@ components: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Sit tempore. required: - config UpdateTwitterAdsRequestBody: @@ -5639,8 +7515,8 @@ components: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Deserunt in hic dolorum facere ipsa et. + label: Repellat veniam. required: - config securitySchemes: @@ -5651,5 +7527,7 @@ components: tags: - name: lfx-v2-campaign-service-svc description: The campaign service supports Marketing Operations campaign creation and management. + - name: lfx-v2-campaign-service-briefs + description: Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation. - name: lfx-v2-campaign-service-connections description: Manage a project's singleton, per-provider ad-platform connections. diff --git a/cmd/campaign-service/server.go b/cmd/campaign-service/server.go index 6922ae50..6c536cab 100644 --- a/cmd/campaign-service/server.go +++ b/cmd/campaign-service/server.go @@ -13,10 +13,12 @@ import ( "log/slog" "net/http" "os" - "sync" + "time" + briefsvcsvr "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_briefs/server" connsvcsvr "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_connections/server" svcsvr "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_svc/server" + briefsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" connsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_connections" svc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_svc" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/container" @@ -39,32 +41,37 @@ func StartServer(ctx context.Context, cfg *config.Config) error { // NOTE: debug.LogPayloads() is intentionally NOT applied. Every authenticated // payload carries a BearerToken (a valid JWT), and the connection service's - // create/set-credential payloads carry plaintext provider credentials, so - // enabling it would leak those secrets into logs. debug.HTTP() IS still - // applied below, but in clue v1.2.1 it does not log headers or statuses — it - // only propagates the runtime /debug toggle into each request's context (so - // debug-level logs elsewhere activate); it decodes no payload. + // create/set-credential payloads carry plaintext provider credentials (OAuth + // refresh tokens, client secrets), so enabling it would leak those secrets + // into logs. debug.HTTP() IS still applied below, but in clue v1.2.1 it does + // not log headers or statuses — it only propagates the runtime /debug toggle + // into each request's context (so debug-level logs elsewhere activate); it + // decodes no payload. endpoints := svc.NewEndpoints(cont.Service) - // The container always initializes Connections (NewContainer wires it in both - // the DB and no-DB paths), so construct the endpoints unconditionally. Fail - // loudly if it's unexpectedly nil rather than silently skipping the mount — - // a mis-wired container should crash at startup, not serve 404s on the - // connection routes. + // The container always initializes Connections and Briefs (NewContainer wires + // them in both the DB and no-DB paths), so construct the endpoints + // unconditionally. Fail loudly if either is unexpectedly nil rather than + // silently skipping the mount — a mis-wired container should crash at startup, + // not serve 404s on the connection/brief routes. if cont.Connections == nil { return fmt.Errorf("container misconfigured: Connections service is nil") } connEndpoints := connsvc.NewEndpoints(cont.Connections) + if cont.Briefs == nil { + return fmt.Errorf("container misconfigured: Briefs service is nil") + } + briefEndpoints := briefsvc.NewEndpoints(cont.Briefs) - return handleHTTPServer(ctx, cfg, endpoints, connEndpoints, cont) + return handleHTTPServer(ctx, cfg, endpoints, connEndpoints, briefEndpoints, cont) } -// buildMux constructs the Goa muxer and mounts BOTH the campaign service and the -// connection service onto it. It is a seam so tests can assert that the -// connection routes are actually reachable (the bug this fixes — routes that -// compile but are never mounted return 404) without standing up a full server. -// It returns an error only for a programmer-level mis-wiring (nil connEndpoints). -func buildMux(ctx context.Context, cfg *config.Config, endpoints *svc.Endpoints, connEndpoints *connsvc.Endpoints) (goahttp.Muxer, error) { +// buildMux constructs the Goa muxer and mounts the campaign, connection, and +// brief services onto it. It is a seam so tests can assert that the routes are +// actually reachable (the bug this fixes — routes that compile but are never +// mounted return 404) without standing up a full server. It returns an error +// only for a programmer-level mis-wiring (nil endpoints). +func buildMux(ctx context.Context, cfg *config.Config, endpoints *svc.Endpoints, connEndpoints *connsvc.Endpoints, briefEndpoints *briefsvc.Endpoints) (goahttp.Muxer, error) { mux := goahttp.NewMuxer() if cfg.Debug { debug.MountPprofHandlers(debug.Adapt(mux)) @@ -92,27 +99,39 @@ func buildMux(ctx context.Context, cfg *config.Config, endpoints *svc.Endpoints, ) svcsvr.Mount(mux, server) - // Mount the connection routes unconditionally. connEndpoints is always - // non-nil (StartServer constructs it and fails loudly if the service is - // mis-wired), and the connection service is wired even without a database - // (with a nil repo) so its routes return the typed 503 rather than a bare - // 404. A nil here would be a programmer error, so fail loudly rather than - // silently skipping the mount and serving 404s. + // Mount the connection and brief routes unconditionally. Both endpoint sets + // are always non-nil (StartServer constructs them and fails loudly if a + // service is mis-wired), and both are wired even without a database (nil repo) + // so their routes return the typed 503 rather than a bare 404. A nil here would + // be a programmer error, so fail loudly rather than silently skipping the mount + // and serving 404s. if connEndpoints == nil { return nil, fmt.Errorf("buildMux: connEndpoints is nil (connection routes would be unmounted)") } connServer := connsvcsvr.New(connEndpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, eh, nil) connsvcsvr.Mount(mux, connServer) + if briefEndpoints == nil { + return nil, fmt.Errorf("buildMux: briefEndpoints is nil (brief routes would be unmounted)") + } + briefServer := briefsvcsvr.New(briefEndpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, eh, nil) + briefsvcsvr.Mount(mux, briefServer) + return mux, nil } -func handleHTTPServer(ctx context.Context, cfg *config.Config, endpoints *svc.Endpoints, connEndpoints *connsvc.Endpoints, cont *container.Container) error { - mux, err := buildMux(ctx, cfg, endpoints, connEndpoints) +func handleHTTPServer(ctx context.Context, cfg *config.Config, endpoints *svc.Endpoints, connEndpoints *connsvc.Endpoints, briefEndpoints *briefsvc.Endpoints, cont *container.Container) error { + mux, err := buildMux(ctx, cfg, endpoints, connEndpoints, briefEndpoints) if err != nil { return err } + // Track in-flight handlers so shutdown can wait (bounded) for stragglers to + // return before the DB pool closes. http.Server.Close does not wait for + // handler goroutines, so without this a straggler could touch the pool as it + // closes; the tracker is waited on between the forced Close and cont.Close. + inflight := middleware.NewInflightTracker() + var handler http.Handler = mux handler = middleware.RequestIDMiddleware()(handler) if cfg.Debug { @@ -126,6 +145,12 @@ func handleHTTPServer(ctx context.Context, cfg *config.Config, endpoints *svc.En return p != "/healthz" && p != "/livez" && p != "/readyz" }), ) + // Wrap the inflight tracker OUTERMOST (applied last), so a request is counted + // from the instant it enters the handler chain — before the request-ID / debug / + // OTel wrappers. If it were inner, a handler that has started but not yet reached + // the inner wrapper would be invisible to Wait, and shutdown could observe zero, + // close the pool, and let that straggler touch a closing pool. + handler = inflight.Middleware()(handler) srv := &http.Server{ Addr: cfg.ServerAddress(), @@ -135,7 +160,13 @@ func handleHTTPServer(ctx context.Context, cfg *config.Config, endpoints *svc.En IdleTimeout: constants.DefaultIdleTimeout, } - return runServerWithContext(ctx, srv, cont) + return runServerWithContext(ctx, srv, cont, inflight) +} + +// inflightWaiter is the subset of middleware.InflightTracker the shutdown path +// needs: wait (bounded) for in-flight handlers to return. +type inflightWaiter interface { + Wait(timeout time.Duration) bool } func errorHandler(logCtx context.Context) func(context.Context, http.ResponseWriter, error) { @@ -144,7 +175,7 @@ func errorHandler(logCtx context.Context) func(context.Context, http.ResponseWri } } -func runServerWithContext(ctx context.Context, srv *http.Server, cont *container.Container) error { +func runServerWithContext(ctx context.Context, srv *http.Server, cont *container.Container, inflight inflightWaiter) error { serverErr := make(chan error, 1) go func() { @@ -161,25 +192,75 @@ func runServerWithContext(ctx context.Context, srv *http.Server, cont *container slog.InfoContext(ctx, "shutdown initiated") } - var wg sync.WaitGroup - wg.Add(2) - - go func() { - defer wg.Done() - shutdownCtx, cancel := context.WithTimeout(context.Background(), constants.DefaultShutdownTimeout) - defer cancel() - if err := srv.Shutdown(shutdownCtx); err != nil { - slog.ErrorContext(ctx, "HTTP server shutdown error", log.ErrKey, err) + // Shut down in order, not concurrently: first stop the HTTP server (stops + // accepting new requests and drains in-flight handlers, so no new dispatch + // can be Started), THEN close the container (which drains in-flight dispatch + // and only then closes the DB pool). Running these concurrently risks a + // just-accepted request Starting a dispatch while the pool is being closed. + // + // Each phase gets its OWN bounded deadline rather than sharing one context. + // A shared context would not bound the total: srv.Shutdown could consume the + // whole budget draining HTTP handlers, leaving Container.Close's drain+grace + // (whose orchestrator grace timer is otherwise wall-clock, not ctx-bound) to + // add its full window on top — pushing the real total past + // DefaultShutdownTimeout and risking a SIGKILL mid-drain. Budgeting the + // phases separately makes the total a true sum: httpShutdownTimeout + + // (dispatchDrainTimeout + CancelGracePeriod) <= DefaultShutdownTimeout, which + // container.go asserts at init. + // Shutdown ordering is explicit: + // 1. graceful srv.Shutdown(httpCtx) — stop accepting, drain handlers. + // 2. on httpCtx expiry, force srv.Close() — http.Server.Shutdown RETURNS on + // ctx expiry even if handlers are STILL running; those stragglers could + // then touch the DB pool while container/pool close runs (and pgxpool.Close + // itself can block on a connection a handler still holds). Close() forcibly + // terminates all connections so no handler is still using the pool when it + // closes. Force-close only after a timeout — on a clean Shutdown there are + // no lingering connections and Close() is a harmless no-op. + // 3. wait (bounded) for any in-flight handler goroutine to actually RETURN. + // srv.Close() terminates connections but does NOT wait for the handler + // goroutines themselves, so a straggler could still be executing (and + // touching the pool) after Close returns. The inflight tracker lets us + // wait for those goroutines to finish before closing the pool; the wait + // is bounded by whatever remains of the HTTP-shutdown budget so a hung + // handler can never wedge shutdown past its window. + // 4. THEN cont.Close(closeCtx) — drain in-flight dispatch, then close the pool. + // A residual remains only if a handler outlasts the bounded wait; that window + // is tracked under LFXV2-2665. + // Reserve HandlerDrainTimeout for the post-force-close inflight wait (step 3), + // so the graceful Shutdown gets the rest of the HTTP budget. This guarantees + // the inflight wait has a real budget even when Shutdown times out (which would + // otherwise exhaust the whole HTTP context). + shutdownWait := container.HTTPShutdownTimeout - container.HandlerDrainTimeout + if shutdownWait <= 0 { + // HandlerDrainTimeout must leave room within the overall shutdown budget; + // if misconfigured so it doesn't, fall back to the full budget rather than + // a zero/negative deadline that would force-close immediately. + shutdownWait = container.HTTPShutdownTimeout + } + httpCtx, cancelHTTP := context.WithTimeout(context.Background(), shutdownWait) + defer cancelHTTP() + if err := srv.Shutdown(httpCtx); err != nil { + slog.ErrorContext(ctx, "HTTP server shutdown error; force-closing lingering connections", log.ErrKey, err) + // Shutdown's ctx expired with handlers still running (or it otherwise + // failed). Force-close so no straggler can touch the pool as it closes. + if cerr := srv.Close(); cerr != nil { + slog.ErrorContext(ctx, "HTTP server force-close error", log.ErrKey, cerr) } - }() - - go func() { - defer wg.Done() - if err := cont.Close(); err != nil { - slog.ErrorContext(ctx, "container close error", log.ErrKey, err) + } + // Wait for in-flight handler goroutines to return before closing the pool. On a + // clean Shutdown they are already drained and this returns immediately; on the + // force-close path it waits up to the DEDICATED HandlerDrainTimeout (NOT the + // HTTP context's remaining time — that is exhausted precisely when Shutdown + // timed out and a straggler is running, which would make the wait a no-op). + if inflight != nil { + if !inflight.Wait(container.HandlerDrainTimeout) { + slog.ErrorContext(ctx, "in-flight HTTP handlers did not return before pool close; a straggler may touch the pool (LFXV2-2665)") } - }() - - wg.Wait() + } + closeCtx, cancelClose := context.WithTimeout(context.Background(), container.ContainerCloseTimeout) + defer cancelClose() + if err := cont.Close(closeCtx); err != nil { + slog.ErrorContext(ctx, "container close error", log.ErrKey, err) + } return nil } diff --git a/cmd/campaign-service/server_test.go b/cmd/campaign-service/server_test.go index 8eec48f8..ae592900 100644 --- a/cmd/campaign-service/server_test.go +++ b/cmd/campaign-service/server_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + briefsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" connsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_connections" svc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_svc" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/infrastructure/config" @@ -25,11 +26,13 @@ import ( // (any non-404 status, e.g. 401/503, proves it is mounted). func TestConnectionRoutesAreMounted(t *testing.T) { endpoints := svc.NewEndpoints(service.NewCampaignService(nil)) - // The no-DB connection service is a real Service whose routes return a typed - // 503 rather than 404 — perfect for proving the route is mounted. + // The no-DB connection and brief services are real Services whose routes + // return a typed 503 rather than 404 — perfect for proving the route is + // mounted. connEndpoints := connsvc.NewEndpoints(service.NewConnectionService(nil, nil)) + briefEndpoints := briefsvc.NewEndpoints(service.NewBriefService(nil, nil, nil, nil)) - mux, err := buildMux(context.Background(), &config.Config{}, endpoints, connEndpoints) + mux, err := buildMux(context.Background(), &config.Config{}, endpoints, connEndpoints, briefEndpoints) if err != nil { t.Fatalf("buildMux: %v", err) } @@ -41,6 +44,7 @@ func TestConnectionRoutesAreMounted(t *testing.T) { path string }{ {"connection google-ads create", http.MethodPost, "/projects/proj-123/connection-google-ads"}, + {"brief create", http.MethodPost, "/projects/proj-123/briefs"}, {"campaign health livez", http.MethodGet, "/livez"}, } for _, tc := range cases { @@ -56,12 +60,18 @@ func TestConnectionRoutesAreMounted(t *testing.T) { } } -// TestBuildMuxNilConnEndpointsFailsLoud verifies the fail-loud guard: a nil -// connEndpoints (a programmer-level mis-wiring) returns an error rather than -// silently building a mux with the connection routes unmounted. -func TestBuildMuxNilConnEndpointsFailsLoud(t *testing.T) { +// TestBuildMuxNilEndpointsFailsLoud verifies the fail-loud guard: a nil +// connEndpoints or briefEndpoints (a programmer-level mis-wiring) returns an +// error rather than silently building a mux with those routes unmounted. +func TestBuildMuxNilEndpointsFailsLoud(t *testing.T) { endpoints := svc.NewEndpoints(service.NewCampaignService(nil)) - if _, err := buildMux(context.Background(), &config.Config{}, endpoints, nil); err == nil { - t.Fatal("expected buildMux to fail loudly when connEndpoints is nil, got nil error") + connEndpoints := connsvc.NewEndpoints(service.NewConnectionService(nil, nil)) + briefEndpoints := briefsvc.NewEndpoints(service.NewBriefService(nil, nil, nil, nil)) + + if _, err := buildMux(context.Background(), &config.Config{}, endpoints, nil, briefEndpoints); err == nil { + t.Error("expected buildMux to fail loudly when connEndpoints is nil, got nil error") + } + if _, err := buildMux(context.Background(), &config.Config{}, endpoints, connEndpoints, nil); err == nil { + t.Error("expected buildMux to fail loudly when briefEndpoints is nil, got nil error") } } diff --git a/design/brief.go b/design/brief.go new file mode 100644 index 00000000..fff1b98e --- /dev/null +++ b/design/brief.go @@ -0,0 +1,364 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +// Package design — brief and campaign endpoints. +// +// Hierarchy: Project -> Brief -> Campaigns. A brief is the funnel unit +// (carries program_type) and is shared across channels; campaigns are a +// collection subordinate to the brief. Campaign creation is asynchronous: +// POST returns a job to poll. Every endpoint is gated on campaign_manager at +// the gateway. See docs/api-catalog.md. +package design + +import ( + //nolint:staticcheck // ST1001: the recommended way of using the goa DSL package is with the . import + . "goa.design/goa/v3/dsl" +) + +// ─── Brief types ─── + +// BriefInput is the mutable brief payload (create/replace). +var BriefInput = Type("brief-input", func() { + Attribute("program_type", String, "Funnel context", func() { + Enum("events", "education", "membership") + }) + Attribute("event_slug", String, "Event/course slug (unique within the project)") + Attribute("url", String, "Event/course page URL") + Attribute("platforms", ArrayOf(String), "Suggested default platforms (a planning hint; binding selection is on the campaign)") + Attribute("event_details", Any, "Extracted event/course details") + Attribute("copy", Any, "Ad copy") + Attribute("keywords", Any, "Keyword list") + Attribute("targeting", Any, "Targeting recommendation") + Required("program_type", "event_slug") +}) + +// Brief is the brief response view. It Reference()s BriefInput so the eight +// shared attributes (program_type, event_slug, url, platforms, event_details, +// copy, keywords, targeting) inherit their type/validation/description from a +// single source of truth — a later change to BriefInput's program_type Enum or a +// validation rule flows here automatically, so the two can't silently drift. +// Brief then layers on its response-only fields. +var Brief = Type("brief", func() { + Reference(BriefInput) + Attribute("id", String, "Brief UUID") + Attribute("project_id", String, "Owning project") + // Inherited from BriefInput via Reference (name-only Attribute calls). + Attribute("program_type") + Attribute("event_slug") + Attribute("url") + Attribute("platforms") + Attribute("event_details") + Attribute("copy") + Attribute("keywords") + Attribute("targeting") + // Response-only fields. + Attribute("status", String, "Lifecycle status", func() { + Enum("draft", "approved", "archived") + }) + Attribute("version", Int64, "Optimistic-concurrency version") + Attribute("etag", String, "ETag header value (mirrors version)") + Required("id", "project_id", "program_type", "event_slug", "status", "version") +}) + +// ─── Campaign / job types ─── + +// CampaignCreateInput selects the platforms to launch on and their config. +var CampaignCreateInput = Type("campaign-create-input", func() { + Attribute("platforms", ArrayOf(String, func() { + // Constrain to the known providers so OpenAPI clients can discover the + // valid values and Goa advertises them; the service also revalidates. + Enum("google-ads", "linkedin-ads", "meta-ads", "reddit-ads", "twitter-ads", "microsoft-ads", "hubspot") + }), "Platforms to create campaigns on (binding selection)", func() { + // Reject an empty array in the schema (the handler also rejects it). Note: + // Goa/OpenAPI can't express uniqueItems, so duplicate rejection stays in + // the handler. + MinLength(1) + }) + Attribute("config", Any, "Per-platform campaign configuration") + Required("platforms") +}) + +// JobCreateResponse is returned immediately from POST .../campaigns. +var JobCreateResponse = Type("job-create-response", func() { + Attribute("job_id", String, "Poll GET /projects/{projectId}/jobs/{jobId}") + Attribute("status", String, "Initial status (always 'queued' on create)", func() { + Enum("queued") + }) + Attribute("platforms", ArrayOf(String), "Platforms this job will create on") + Required("job_id", "status", "platforms") +}) + +// PlatformResult is one platform's outcome within a terminal job result. It +// mirrors exactly what the orchestrator emits so the OpenAPI can describe the +// result array instead of an opaque Any. +var PlatformResult = Type("platform-result", func() { + Attribute("platform", String, "Platform this result is for") + Attribute("ok", Boolean, "Whether the campaign was created (or reused) successfully") + Attribute("campaign_id", String, "Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost.") + Attribute("error", String, "Failure reason (present when not ok)") + Required("platform", "ok") +}) + +// JobPollResponse is returned from GET .../jobs/{jobId}. +var JobPollResponse = Type("job-poll-response", func() { + Attribute("job_id", String, "Job UUID") + Attribute("status", String, "Job status", func() { + Enum("queued", "running", "succeeded", "partial", "failed") + }) + Attribute("result", ArrayOf(PlatformResult), "Per-platform results, written once when the job reaches a terminal state") + Attribute("error", String, "Terminal error, if any") + Required("job_id", "status") +}) + +// Campaign is a single platform's campaign under a brief. +var Campaign = Type("campaign", func() { + Attribute("id", String, "Campaign UUID") + Attribute("project_id", String, "Owning project") + Attribute("brief_id", String, "Parent brief") + Attribute("platform", String, "Channel") + Attribute("platform_campaign_id", String, "ID returned by the ad platform") + Attribute("campaign_name", String, "Campaign name") + Attribute("status", String, "Campaign status") + Attribute("version", Int64, "Optimistic-concurrency version") + Attribute("etag", String, "ETag header value (mirrors version)") + Required("id", "project_id", "brief_id", "platform", "campaign_name", "status", "version") +}) + +// CampaignUpdateInput is the mutable campaign payload (replace). +var CampaignUpdateInput = Type("campaign-update-input", func() { + Attribute("campaign_name", String, "Campaign name") + Attribute("status", String, "Campaign status") + Attribute("config", Any, "Campaign configuration snapshot") + Required("campaign_name", "status") +}) + +// ─── Brief + campaign service ─── + +var _ = Service("lfx-v2-campaign-service-briefs", func() { + Description("Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation.") + + Security(JWTAuth) + + Method("create-brief", func() { + Description("Create a brief.") + Payload(func() { + bearerToken() + projectIDAttr() + Attribute("brief", BriefInput) + Required("project_id", "brief") + }) + Result(Brief) + commonBriefErrors(true) + HTTP(func() { + POST("/projects/{project_id}/briefs") + Header("bearer_token:Authorization") + Response(StatusCreated, func() { Header("etag:ETag") }) + briefErrorResponses(true) + }) + }) + + Method("get-brief", func() { + Description("Get a brief; returns ETag.") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + Required("project_id", "brief_id") + }) + Result(Brief) + commonBriefErrors(false) + HTTP(func() { + GET("/projects/{project_id}/briefs/{brief_id}") + Header("bearer_token:Authorization") + Response(StatusOK, func() { Header("etag:ETag") }) + briefErrorResponses(false) + }) + }) + + Method("update-brief", func() { + Description("Replace a brief (requires If-Match).") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + ifMatchAttr() + Attribute("brief", BriefInput) + Required("project_id", "brief_id", "brief") + }) + Result(Brief) + commonBriefErrors(true) + Error("PreconditionFailed", PreconditionFailedError, "ETag mismatch") + Error("PreconditionRequired", PreconditionRequiredError, "If-Match header required") + HTTP(func() { + PUT("/projects/{project_id}/briefs/{brief_id}") + Header("bearer_token:Authorization") + Header("if_match:If-Match") + Response(StatusOK, func() { Header("etag:ETag") }) + briefErrorResponses(true) + Response("PreconditionFailed", StatusPreconditionFailed) + Response("PreconditionRequired", StatusPreconditionRequired) + }) + }) + + Method("approve-brief", func() { + Description("Approve a brief for campaign creation.") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + // Reuse the shared If-Match attribute (carries Example("3")) so Goa + // generates a valid numeric CLI example instead of a prose placeholder + // that parseBriefIfMatch would reject. + ifMatchAttr() + Required("project_id", "brief_id") + }) + Result(Brief) + commonBriefErrors(false) + Error("PreconditionFailed", PreconditionFailedError, "ETag mismatch") + Error("PreconditionRequired", PreconditionRequiredError, "If-Match header required") + HTTP(func() { + POST("/projects/{project_id}/briefs/{brief_id}/approve") + Header("bearer_token:Authorization") + Header("if_match:If-Match") + Response(StatusOK, func() { Header("etag:ETag") }) + briefErrorResponses(false) + Response("PreconditionFailed", StatusPreconditionFailed) + Response("PreconditionRequired", StatusPreconditionRequired) + }) + }) + + Method("delete-brief", func() { + Description("Archive a brief (soft delete).") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + Required("project_id", "brief_id") + }) + commonBriefErrors(false) + HTTP(func() { + DELETE("/projects/{project_id}/briefs/{brief_id}") + Header("bearer_token:Authorization") + Response(StatusNoContent) + briefErrorResponses(false) + }) + }) + + Method("create-campaigns", func() { + Description("Create campaigns across the selected platforms (async -> job).") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + Attribute("input", CampaignCreateInput) + Required("project_id", "brief_id", "input") + }) + Result(JobCreateResponse) + commonBriefErrors(true) + HTTP(func() { + POST("/projects/{project_id}/briefs/{brief_id}/campaigns") + Header("bearer_token:Authorization") + Response(StatusAccepted) + briefErrorResponses(true) + }) + }) + + Method("get-campaign", func() { + Description("Get one campaign under a brief; returns ETag.") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + campaignIDAttr() + Required("project_id", "brief_id", "campaign_id") + }) + Result(Campaign) + commonBriefErrors(false) + HTTP(func() { + GET("/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}") + Header("bearer_token:Authorization") + Response(StatusOK, func() { Header("etag:ETag") }) + briefErrorResponses(false) + }) + }) + + Method("update-campaign", func() { + Description("Replace a campaign (requires If-Match).") + Payload(func() { + bearerToken() + projectIDAttr() + briefIDAttr() + campaignIDAttr() + ifMatchAttr() + Attribute("campaign", CampaignUpdateInput) + Required("project_id", "brief_id", "campaign_id", "campaign") + }) + Result(Campaign) + commonBriefErrors(true) + Error("PreconditionFailed", PreconditionFailedError, "ETag mismatch") + Error("PreconditionRequired", PreconditionRequiredError, "If-Match header required") + HTTP(func() { + PUT("/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}") + Header("bearer_token:Authorization") + Header("if_match:If-Match") + Response(StatusOK, func() { Header("etag:ETag") }) + briefErrorResponses(true) + Response("PreconditionFailed", StatusPreconditionFailed) + Response("PreconditionRequired", StatusPreconditionRequired) + }) + }) + + Method("get-job", func() { + Description("Poll campaign-creation job status.") + Payload(func() { + bearerToken() + projectIDAttr() + Attribute("job_id", String, "Job UUID", func() { Format(FormatUUID) }) + Required("project_id", "job_id") + }) + Result(JobPollResponse) + commonBriefErrors(false) + HTTP(func() { + GET("/projects/{project_id}/jobs/{job_id}") + Header("bearer_token:Authorization") + Response(StatusOK) + briefErrorResponses(false) + }) + }) +}) + +// ─── shared DSL helpers for briefs ─── + +func briefIDAttr() { + Attribute("brief_id", String, "Brief UUID", func() { Format(FormatUUID) }) +} + +func campaignIDAttr() { + Attribute("campaign_id", String, "Campaign UUID", func() { Format(FormatUUID) }) +} + +// commonBriefErrors declares the standard error set for a brief method. +// BadRequest is always declared: JWTAuth can reject any method with a 400 +// (malformed/invalid token) regardless of whether the method takes a body, so +// every method's generated encoder must handle it. The withBadRequest parameter +// is retained for call-site readability (true = the method also validates a body) +// but no longer gates BadRequest. +func commonBriefErrors(withBadRequest bool) { + _ = withBadRequest + Error("BadRequest", BadRequestError, "Bad request") + Error("NotFound", NotFoundError, "Resource not found") + Error("Conflict", ConflictError, "Conflict") + Error("InternalServerError", InternalServerError, "Internal server error") + Error("ServiceUnavailable", ConnServiceUnavailableError, "Service unavailable") +} + +// briefErrorResponses maps the standard errors to HTTP responses. BadRequest is +// always mapped to match commonBriefErrors. +func briefErrorResponses(withBadRequest bool) { + _ = withBadRequest + Response("BadRequest", StatusBadRequest) + Response("NotFound", StatusNotFound) + Response("Conflict", StatusConflict) + Response("InternalServerError", StatusInternalServerError) + Response("ServiceUnavailable", StatusServiceUnavailable) +} diff --git a/docs/api-catalog.md b/docs/api-catalog.md index 099da9c4..15e8d6b5 100644 --- a/docs/api-catalog.md +++ b/docs/api-catalog.md @@ -32,7 +32,7 @@ A brief is the funnel unit: it carries the **program** (`program_type` = events | GET | `/projects/{projectId}/briefs/{id}` | `campaign_manager` | JSON | Get a brief (full copy, keywords, targeting); returns ETag. | | PUT | `/projects/{projectId}/briefs/{id}` | `campaign_manager` | JSON | Replace a brief (requires `If-Match`). | | POST | `/projects/{projectId}/briefs/{id}/refresh` | `campaign_manager` | JSON | Re-run generation against latest event data, producing a new version. | -| POST | `/projects/{projectId}/briefs/{id}/approve` | `campaign_manager` | JSON | Approve a brief for campaign creation. | +| POST | `/projects/{projectId}/briefs/{id}/approve` | `campaign_manager` | JSON | Approve a brief for campaign creation (requires `If-Match`; approval is version-gated so a brief replaced since it was fetched cannot be approved on stale content). | | DELETE | `/projects/{projectId}/briefs/{id}` | `campaign_manager` | JSON | Archive a brief (soft delete). | > Listing briefs and viewing a brief's version history are served by the Query Service, not by dedicated endpoints here. @@ -276,13 +276,31 @@ platforms: CampaignPlatform[] — Platforms this job will create on (echoed fr jobId: string status: 'queued' | 'running' | 'succeeded' | 'partial' | 'failed' — 'partial' = some platforms succeeded, some failed -results: CampaignCreateResult[] — Per-platform results, populated as each platform completes -errors: string[] — Platform-specific errors if any -startedAt: string — ISO timestamp -finishedAt?: string — ISO timestamp, present once terminal +result?: PlatformResult[] — Per-platform results, written once when the job + reaches a terminal state (absent while queued/running) +error?: string — Terminal error, if the job failed as a whole ``` -### CampaignCreateResult (per-platform result, embedded in JobPollResponse.results) +### PlatformResult (per-platform outcome, embedded in JobPollResponse.result) + +``` +platform: string — Platform this result is for +ok: boolean — Whether the campaign was created (or reused) successfully +campaignId?: string — Upstream platform campaign id (present when ok) +error?: string — Failure reason (present when not ok) +``` + +Per-platform errors are carried inside each `result` entry rather than in a +separate top-level array; the job's own start/finish times are available from +the job record's timestamps and are not echoed in the poll payload. + +### CampaignCreateResult (future, richer per-platform result) + +> Not yet emitted. Today the job result carries the minimal `PlatformResult` +> shape above (`platform`/`ok`/`campaignId`/`error`). Once the per-provider +> dispatchers land, each result is expected to grow into the richer shape below +> (counts, creation log, direct UI URL); this section documents that intended +> end-state, not the current payload. ``` platform: CampaignPlatform diff --git a/docs/architecture.md b/docs/architecture.md index fd8bc7bf..a41bf113 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -356,7 +356,7 @@ erDiagram campaign_briefs { UUID id PK - UUID project_id + TEXT project_id "project UUID or slug (e.g. cncf)" VARCHAR program_type "events/education/membership" VARCHAR event_slug "UNIQUE with project_id" TEXT url @@ -375,7 +375,7 @@ erDiagram campaigns { UUID id PK - UUID project_id + TEXT project_id "project UUID or slug (e.g. cncf)" UUID brief_id FK "many campaigns share one brief" UUID job_id "soft ref to campaign_jobs; no FK (jobs are ephemeral, expire/purge)" VARCHAR platform "channel: google-ads/linkedin-ads/..." @@ -470,7 +470,7 @@ Beyond paid ad platforms, the campaign service will manage connections to organi Persistence uses **strongly-typed, one-table-per-provider** schemas rather than a single generic `channel_connections` table with an untyped `metadata` blob. Credentials and configuration differ materially between providers, so folding them into untyped JSON would weaken the contract and make the API harder to develop and validate against (especially for agents). Each supported provider gets its own table with provider-specific columns. -Every table that maps to an LFX platform resource type carries a `version BIGINT` iterator that powers the platform-idiomatic **ETag / `If-Match`** optimistic-concurrency controls on `PUT`s (pattern per [2026-05-CloudNativePG](https://github.com/linuxfoundation/lfx-architecture-scratch/tree/main/2026-05-CloudNativePG)). `project_id` is a plain indexed `UUID` (owned by the project-service; no cross-service FK). Credentials are encrypted at the application layer (AES-256-GCM) using a key from a Kubernetes secret — not pgcrypto. +Every table that maps to an LFX platform resource type carries a `version BIGINT` iterator that powers the platform-idiomatic **ETag / `If-Match`** optimistic-concurrency controls on `PUT`s (pattern per [2026-05-CloudNativePG](https://github.com/linuxfoundation/lfx-architecture-scratch/tree/main/2026-05-CloudNativePG)). `project_id` is a plain indexed column (owned by the project-service; no cross-service FK) typed `TEXT`, because a project is addressed by its UUID **or** its slug (e.g. `cncf`) and the API contract advertises the same — the `campaign_briefs`/`campaigns` tables use `TEXT project_id` accordingly (migration `000003`). Credentials are encrypted at the application layer (AES-256-GCM) using a key from a Kubernetes secret — not pgcrypto. The full table definitions — per-provider connection tables **plus** `campaign_briefs` and `campaigns` — live in [channel-connections-schema.md](channel-connections-schema.md) and are not duplicated here. For briefs and campaigns, attribution and revision history are served by the Query Service on each (re)index, so this service keeps no separate audit tables. Connection tables are not indexed (singleton per project; no listing consumer). diff --git a/docs/channel-connections-schema.md b/docs/channel-connections-schema.md index f46f74f0..6dcf72ed 100644 --- a/docs/channel-connections-schema.md +++ b/docs/channel-connections-schema.md @@ -163,7 +163,7 @@ Beyond connections, the service persists briefs and campaigns. Both carry the `v ```sql CREATE TABLE campaign_briefs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - project_id UUID NOT NULL, + project_id TEXT NOT NULL, -- project UUID or slug (migration 000003) program_type TEXT NOT NULL -- funnel context CHECK (program_type IN ('events','education','membership')), event_slug TEXT NOT NULL, -- UNIQUE with project_id @@ -179,9 +179,13 @@ CREATE TABLE campaign_briefs ( approved_by JSONB, -- {name,email,username} or null approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - UNIQUE (project_id, event_slug) + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); +-- (project_id, event_slug) uniqueness is a PARTIAL unique index excluding +-- archived briefs (migration 000003), so archiving a brief frees the slot for a +-- new brief with the same slug: +CREATE UNIQUE INDEX uq_campaign_briefs_project_event + ON campaign_briefs (project_id, event_slug) WHERE status <> 'archived'; CREATE INDEX idx_campaign_briefs_project_id ON campaign_briefs (project_id); ``` @@ -190,7 +194,7 @@ CREATE INDEX idx_campaign_briefs_project_id ON campaign_briefs (project_id); ```sql CREATE TABLE campaigns ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - project_id UUID NOT NULL, + project_id TEXT NOT NULL, -- project UUID or slug (migration 000003) brief_id UUID NOT NULL REFERENCES campaign_briefs(id), -- many campaigns per brief job_id UUID, -- creation job that produced this row platform TEXT NOT NULL, -- channel: google-ads / linkedin-ads / ... diff --git a/docs/knowledge/code/design.md b/docs/knowledge/code/design.md index 099e203f..35b73fae 100644 --- a/docs/knowledge/code/design.md +++ b/docs/knowledge/code/design.md @@ -9,4 +9,14 @@ resource: "design" Package design contains the DSL for the campaign service Goa API generation. +It defines three services: the health service (`readyz`/`livez`), the +connections service (per-provider singleton credential CRUD), and the briefs +service. The briefs service models the Project → Brief → Campaigns hierarchy: +brief CRUD (the funnel unit, carrying `program_type`), asynchronous campaign +creation (`POST .../campaigns` returns a job to poll), and campaign read/update. +Every method is gated on `campaign_manager` at the gateway via `JWTAuth`, which +can reject any request with a `BadRequest` (400) — so every brief method +declares `BadRequest` regardless of whether it accepts a body. The binding +`platforms` selection is constrained to the known provider enum. + See [design](../../../design). diff --git a/docs/knowledge/code/internal-container.md b/docs/knowledge/code/internal-container.md index caf92d2a..f7939a71 100644 --- a/docs/knowledge/code/internal-container.md +++ b/docs/knowledge/code/internal-container.md @@ -10,9 +10,13 @@ resource: "internal/container" Package container provides dependency injection for the application. When a database URL is configured it validates settings, runs migrations, -opens an instrumented `postgres.Pool`, and passes the pool to -`NewCampaignService` so `/readyz` reflects DB connectivity. Without a -database URL the health service still starts and connection routes return -typed `503` responses. +opens an instrumented `postgres.Pool`, and wires the services against it: the +connection service (with its repo and the AES-GCM credential encryptor), the +brief service and its async orchestrator (brief/campaign/job repos), and the +campaign/health service so `/readyz` reflects DB connectivity. No platform +dispatchers are registered yet, so campaign creation records jobs but performs +no upstream dispatch (a startup warning notes this). Without a database URL the +health service still starts and the connection and brief routes return typed +`503` responses rather than unmounted `404`s. See [internal/container](../../../internal/container). diff --git a/docs/knowledge/code/internal-infrastructure-postgres.md b/docs/knowledge/code/internal-infrastructure-postgres.md index 4c98c327..06521141 100644 --- a/docs/knowledge/code/internal-infrastructure-postgres.md +++ b/docs/knowledge/code/internal-infrastructure-postgres.md @@ -14,4 +14,23 @@ with `otelpgx`), migration runner, and repository implementations. `ReadinessChecker` interface. Pool open fails fast on ping failure so unreachable databases do not wedge startup. +## Migrations + +SQL migrations live under `migrations/` and are embedded (`//go:embed *.sql`) +so golang-migrate's iofs source can run them from the compiled binary. Each +version is a paired `NNNNNN_name.up.sql` / `.down.sql`; applied versions are +never re-run, so a schema change is always a NEW version, never an edit to an +applied file. + +- `000001` — connection tables. +- `000002` — brief, campaign, and async-job tables. Indexes: `campaign_jobs` + on `brief_id`; `campaigns` on `project_id`. `(brief_id, platform)` / + `(project_id, event_slug)` uniqueness covers those leftmost columns. +- `000003` — brief `project_id` UUID→TEXT and partial-unique + `(project_id, event_slug)` excluding archived rows. +- `000004` — partial index `idx_campaign_jobs_recovery` on + `campaign_jobs (updated_at) WHERE status IN ('queued','running')`, supporting + the periodic stuck-job recovery sweep (`JobRepo.FailStuckJobs`) so it does not + full-scan campaign_jobs as terminal job history grows. + See [internal/infrastructure/postgres](../../../internal/infrastructure/postgres). diff --git a/docs/knowledge/code/internal-service.md b/docs/knowledge/code/internal-service.md index ba64b8a1..31cae74c 100644 --- a/docs/knowledge/code/internal-service.md +++ b/docs/knowledge/code/internal-service.md @@ -8,11 +8,38 @@ resource: "internal/service" # internal/service Package service contains the campaign service business logic, including the -implementation of the generated Goa service interface. +implementation of the generated Goa service interfaces. `GET /readyz` ANDs an optional `ReadinessChecker` (the PostgreSQL pool when wired) into readiness with a ~2s timeout and returns `503` when the dependency is unhealthy. `GET /livez` remains process-only so database outages do not trigger Kubernetes restarts. +`BriefService` implements brief CRUD and campaign endpoints. Campaign creation +(`CreateCampaigns`) requires an approved brief, rejects empty and duplicate +platform sets (a duplicate would create two paid upstream campaigns), then hands +off to the `Orchestrator`, which persists a job and dispatches per platform +asynchronously (bounded concurrency). Dispatch is idempotent: a brief already +carrying a campaign with an upstream id for a platform is reused rather than +re-created. The idempotency fast-path lookup (`GetCampaignByPlatform`) +distinguishes its outcomes: an existing campaign with an upstream id short-circuits +to reuse; `ErrNotFound` (no row yet) falls through to `ClaimCampaignDispatch`; but a +REAL DB error (anything else) is surfaced as a platform failure (logged at ERROR) +rather than silently treated like "no existing campaign" — proceeding to +claim/dispatch when an existing campaign merely couldn't be loaded could duplicate an +upstream create, so it fails loud instead. Replacing a brief's +content resets it to `draft` (re-approval required). Optimistic concurrency is enforced via +version/If-Match (`428` when missing, `412` on mismatch). + +Dispatch is durable (LFXV2-2665): single-flight per (brief, platform) is +enforced by an atomic claim — `ClaimCampaignDispatch` does INSERT ... ON CONFLICT +DO NOTHING of a `pending` campaign row, so exactly one worker across replicas +wins the claim (the unique index arbitrates) with no held connection or blocking +lock. A worker that loses the claim reuses the existing row instead of dispatching +again; the pending row also survives an upstream-create-then-crash, making the +orphaned upstream campaign recoverable. The orchestrator tracks in-flight runs +and its `Shutdown` drains them (bounded) before the DB pool closes, and on +startup jobs left non-terminal beyond a staleness cutoff are failed-forward (they +cannot be safely resumed without provider idempotency keys). + See [internal/service](../../../internal/service). diff --git a/docs/knowledge/log.md b/docs/knowledge/log.md index 5699eb3a..802f0936 100644 --- a/docs/knowledge/log.md +++ b/docs/knowledge/log.md @@ -2,6 +2,31 @@ ## 2026-07-20 +**Update** — Idempotency-lookup errors no longer silently fall through to dispatch +(PR #11 review, cursor Medium). In `dispatchPlatform`, the fast path treated ANY +non-nil error from `GetCampaignByPlatform` like "no row" and fell through to +claim/dispatch — so a transient/real DB failure that hid an existing campaign could +trigger a duplicate upstream create, with no log/signal. Now the outcomes are +distinguished: existing-with-upstream-id → reuse; `ErrNotFound` → fall through to the +claim; any OTHER error → surface as a platform failure (logged ERROR), not a blind +dispatch. Corrected the concept doc, which had documented the old swallow-the-error +behavior as intentional. Test added (`TestOrchestrator_IdempotencyLookupErrorIsFailure`). + +**Update** — Addressed dealako's 4 [minor] review items on PR #11 (LFXV2-2626). +(1) `GetCampaignByPlatform` was the one campaign_repo method not scoped by +project_id — added a `projectID` param + `AND project_id=$3` (matching +GetCampaign/ClaimCampaignDispatch) for tenant-isolation defense-in-depth; updated +the domain interface + the orchestrator call site. (2) The rare double-fault in +`ClaimCampaignDispatch` (post-insert read AND rollback both fail) orphans a +`status='pending'` row that permanently blocks the (brief,platform) pair — now +logs at ERROR with project_id/brief_id/platform/job_id for alerting/manual +reconcile. (3) Added `TestClaimCampaignDispatch_ConcurrentSingleWinner` — N +goroutines racing the claim path, asserting exactly one wins and losers cleanly +no-op (the prior claim tests were single-threaded). (4) `design/brief.go`: `Brief` +now `Reference()`s `BriefInput` for the 8 shared attributes instead of +duplicating them — this also fixed a latent drift the manual copy had already +caused (Brief's `program_type` was missing BriefInput's Enum, so the generated +OpenAPI had no enum + gibberish examples on the Brief response; regenerated). **Update** — Closed the second half of the X/Twitter URL leak (PR #31 review, copilot). The transportError fix covered the AMBIGUOUS branch, but the PRE-SEND branch (`isPreSendDialError` → DNS/connect-refused) still did a raw @@ -242,6 +267,49 @@ must also be mounted in `server.go`, or its routes 404 despite compiling. **Creation** — Added the `internal/platform/reddit` concept doc for the new Reddit Ads API v3 client (OAuth2 token refresh + Campaign -> Ad Group -> Ad creation) and listed it in the code index. +**Update** — Hardened claim-based dispatch: resolve the dispatcher and reuse an +already-completed campaign BEFORE claiming (so a no-dispatcher platform never +leaves a permanent pending claim), release the pending claim if dispatch fails +before the upstream campaign is created, and bound concurrent provider calls with +a process-wide semaphore (previously the per-job errgroup limit let N concurrent +jobs each get maxParallelDispatch slots). Shutdown cancels in-flight runs on +drain timeout. + +**Update** — Reworked LFXV2-2665 single-flight from a held-connection advisory +lock to an atomic claim row (INSERT ON CONFLICT DO NOTHING of a `pending` +campaign), removing the pool-exhaustion/blocking hazards of holding a connection +across the HTTP dispatch. The pending row is also the recovery signal for an +upstream-create-then-crash. Recovery scan uses a staleness cutoff so a rolling +deploy can't fail a job the old replica is still dispatching. + +**Update** — Durable campaign dispatch (LFXV2-2665): per-platform single-flight +via an atomic claim row (ClaimCampaignDispatch — INSERT ON CONFLICT DO NOTHING of +a 'pending' campaign; see the later hardening entries above for the final shape, +which superseded an initial advisory-lock attempt), so concurrent +create-campaigns can't double-create upstream; the orchestrator drains in-flight +runs on graceful shutdown before the pool closes; and startup fails-forward jobs +left non-terminal by a restart. Added CampaignRepository.ClaimCampaignDispatch / +DeleteDispatchClaim and JobRepository.FailStuckJobs. + +**Update** — PR #11 review round 3: validate brief_id/campaign_id/job_id path +params as UUIDs (400 instead of a PostgreSQL cast 500); make brief approval +version-gated via If-Match (rejects approving stale content, 412/428); type the +job-poll result (PlatformResult array, replacing Any); and stop applying +debug.LogPayloads to the connection/brief/health endpoints so DEBUG can't leak +BearerTokens or plaintext provider credentials into logs (debug.HTTP header/status +logging is retained). Reconciled api-catalog (PlatformResult; CampaignCreateResult +marked as the future richer shape). + +**Update** — Brief + campaign API and async orchestrator (LFXV2-2626): +updated `design`, `internal/service`, and `internal/container` concepts for +the Project → Brief → Campaigns hierarchy, async job dispatch, and idempotent +per-platform creation. Behavior hardened per review: brief content replace +resets status to `draft` and persists `event_slug`; duplicate platform sets are +rejected; dispatch reuses an existing upstream campaign instead of re-creating; +brief responses carry `event_details`/`copy`/`keywords`/`targeting`; the +`(project_id, event_slug)` archived-aware partial unique index moved to a new +migration `000003` (never edit an applied migration in place); `platforms` is +enum-constrained and every brief method declares `BadRequest` (JWTAuth can 400). **Creation** — Added OKF concept doc for internal/platform/linkedin (LinkedIn Marketing API client), listed in the code index. diff --git a/gen/http/cli/lfx_v2_campaign_service/cli.go b/gen/http/cli/lfx_v2_campaign_service/cli.go index c1b47439..50e2f130 100644 --- a/gen/http/cli/lfx_v2_campaign_service/cli.go +++ b/gen/http/cli/lfx_v2_campaign_service/cli.go @@ -13,6 +13,7 @@ import ( "net/http" "os" + lfxv2campaignservicebriefsc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_briefs/client" lfxv2campaignserviceconnectionsc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_connections/client" lfxv2campaignservicesvcc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/http/lfx_v2_campaign_service_svc/client" goahttp "goa.design/goa/v3/http" @@ -24,6 +25,7 @@ import ( // command (subcommand1|subcommand2|...) func UsageCommands() []string { return []string{ + "lfx-v2-campaign-service-briefs (create-brief|get-brief|update-brief|approve-brief|delete-brief|create-campaigns|get-campaign|update-campaign|get-job)", "lfx-v2-campaign-service-connections (create-google-ads|get-google-ads|update-google-ads|delete-google-ads|test-google-ads|set-credential-google-ads|create-linkedin-ads|get-linkedin-ads|update-linkedin-ads|delete-linkedin-ads|test-linkedin-ads|set-credential-linkedin-ads|create-meta-ads|get-meta-ads|update-meta-ads|delete-meta-ads|test-meta-ads|set-credential-meta-ads|create-reddit-ads|get-reddit-ads|update-reddit-ads|delete-reddit-ads|test-reddit-ads|set-credential-reddit-ads|create-twitter-ads|get-twitter-ads|update-twitter-ads|delete-twitter-ads|test-twitter-ads|set-credential-twitter-ads|create-microsoft-ads|get-microsoft-ads|update-microsoft-ads|delete-microsoft-ads|test-microsoft-ads|set-credential-microsoft-ads|create-hubspot|get-hubspot|update-hubspot|delete-hubspot|test-hubspot|set-credential-hubspot)", "lfx-v2-campaign-service-svc (readyz|livez)", } @@ -31,7 +33,8 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "lfx-v2-campaign-service-connections create-google-ads --body '{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Recusandae nam autem dicta perspiciatis quod.\",\n \"client_secret\": \"Quo voluptatem quas culpa omnis consequuntur iste.\",\n \"developer_token\": \"Id magnam id nihil fugiat.\",\n \"refresh_token\": \"Inventore quisquam laudantium velit.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"" + "\n" + + return os.Args[0] + " " + "lfx-v2-campaign-service-briefs create-brief --body '{\n \"brief\": {\n \"copy\": \"Autem veniam.\",\n \"event_details\": \"Vel autem voluptates.\",\n \"event_slug\": \"Et est laboriosam.\",\n \"keywords\": \"Enim unde omnis.\",\n \"platforms\": [\n \"Nam exercitationem consectetur sint.\",\n \"Necessitatibus ex ea ullam corrupti.\",\n \"Et omnis accusantium.\",\n \"Iure modi asperiores ut.\"\n ],\n \"program_type\": \"education\",\n \"targeting\": \"Quam sed rem quia consequuntur ut.\",\n \"url\": \"Facere sint totam sapiente vitae numquam ut.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"" + "\n" + + os.Args[0] + " " + "lfx-v2-campaign-service-connections create-google-ads --body '{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Architecto itaque doloribus voluptatem consequatur quis fugiat.\",\n \"client_secret\": \"Recusandae qui culpa.\",\n \"developer_token\": \"Accusamus et atque enim.\",\n \"refresh_token\": \"Dolorum minus voluptatem.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"" + "\n" + os.Args[0] + " " + "lfx-v2-campaign-service-svc readyz" + "\n" + "" } @@ -46,6 +49,61 @@ func ParseEndpoint( restore bool, ) (goa.Endpoint, any, error) { var ( + lfxV2CampaignServiceBriefsFlags = flag.NewFlagSet("lfx-v2-campaign-service-briefs", flag.ContinueOnError) + + lfxV2CampaignServiceBriefsCreateBriefFlags = flag.NewFlagSet("create-brief", flag.ExitOnError) + lfxV2CampaignServiceBriefsCreateBriefBodyFlag = lfxV2CampaignServiceBriefsCreateBriefFlags.String("body", "REQUIRED", "") + lfxV2CampaignServiceBriefsCreateBriefProjectIDFlag = lfxV2CampaignServiceBriefsCreateBriefFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsCreateBriefBearerTokenFlag = lfxV2CampaignServiceBriefsCreateBriefFlags.String("bearer-token", "", "") + + lfxV2CampaignServiceBriefsGetBriefFlags = flag.NewFlagSet("get-brief", flag.ExitOnError) + lfxV2CampaignServiceBriefsGetBriefProjectIDFlag = lfxV2CampaignServiceBriefsGetBriefFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsGetBriefBriefIDFlag = lfxV2CampaignServiceBriefsGetBriefFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsGetBriefBearerTokenFlag = lfxV2CampaignServiceBriefsGetBriefFlags.String("bearer-token", "", "") + + lfxV2CampaignServiceBriefsUpdateBriefFlags = flag.NewFlagSet("update-brief", flag.ExitOnError) + lfxV2CampaignServiceBriefsUpdateBriefBodyFlag = lfxV2CampaignServiceBriefsUpdateBriefFlags.String("body", "REQUIRED", "") + lfxV2CampaignServiceBriefsUpdateBriefProjectIDFlag = lfxV2CampaignServiceBriefsUpdateBriefFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsUpdateBriefBriefIDFlag = lfxV2CampaignServiceBriefsUpdateBriefFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsUpdateBriefBearerTokenFlag = lfxV2CampaignServiceBriefsUpdateBriefFlags.String("bearer-token", "", "") + lfxV2CampaignServiceBriefsUpdateBriefIfMatchFlag = lfxV2CampaignServiceBriefsUpdateBriefFlags.String("if-match", "", "") + + lfxV2CampaignServiceBriefsApproveBriefFlags = flag.NewFlagSet("approve-brief", flag.ExitOnError) + lfxV2CampaignServiceBriefsApproveBriefProjectIDFlag = lfxV2CampaignServiceBriefsApproveBriefFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsApproveBriefBriefIDFlag = lfxV2CampaignServiceBriefsApproveBriefFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsApproveBriefBearerTokenFlag = lfxV2CampaignServiceBriefsApproveBriefFlags.String("bearer-token", "", "") + lfxV2CampaignServiceBriefsApproveBriefIfMatchFlag = lfxV2CampaignServiceBriefsApproveBriefFlags.String("if-match", "", "") + + lfxV2CampaignServiceBriefsDeleteBriefFlags = flag.NewFlagSet("delete-brief", flag.ExitOnError) + lfxV2CampaignServiceBriefsDeleteBriefProjectIDFlag = lfxV2CampaignServiceBriefsDeleteBriefFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsDeleteBriefBriefIDFlag = lfxV2CampaignServiceBriefsDeleteBriefFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsDeleteBriefBearerTokenFlag = lfxV2CampaignServiceBriefsDeleteBriefFlags.String("bearer-token", "", "") + + lfxV2CampaignServiceBriefsCreateCampaignsFlags = flag.NewFlagSet("create-campaigns", flag.ExitOnError) + lfxV2CampaignServiceBriefsCreateCampaignsBodyFlag = lfxV2CampaignServiceBriefsCreateCampaignsFlags.String("body", "REQUIRED", "") + lfxV2CampaignServiceBriefsCreateCampaignsProjectIDFlag = lfxV2CampaignServiceBriefsCreateCampaignsFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsCreateCampaignsBriefIDFlag = lfxV2CampaignServiceBriefsCreateCampaignsFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsCreateCampaignsBearerTokenFlag = lfxV2CampaignServiceBriefsCreateCampaignsFlags.String("bearer-token", "", "") + + lfxV2CampaignServiceBriefsGetCampaignFlags = flag.NewFlagSet("get-campaign", flag.ExitOnError) + lfxV2CampaignServiceBriefsGetCampaignProjectIDFlag = lfxV2CampaignServiceBriefsGetCampaignFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsGetCampaignBriefIDFlag = lfxV2CampaignServiceBriefsGetCampaignFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsGetCampaignCampaignIDFlag = lfxV2CampaignServiceBriefsGetCampaignFlags.String("campaign-id", "REQUIRED", "Campaign UUID") + lfxV2CampaignServiceBriefsGetCampaignBearerTokenFlag = lfxV2CampaignServiceBriefsGetCampaignFlags.String("bearer-token", "", "") + + lfxV2CampaignServiceBriefsUpdateCampaignFlags = flag.NewFlagSet("update-campaign", flag.ExitOnError) + lfxV2CampaignServiceBriefsUpdateCampaignBodyFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("body", "REQUIRED", "") + lfxV2CampaignServiceBriefsUpdateCampaignProjectIDFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsUpdateCampaignBriefIDFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("brief-id", "REQUIRED", "Brief UUID") + lfxV2CampaignServiceBriefsUpdateCampaignCampaignIDFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("campaign-id", "REQUIRED", "Campaign UUID") + lfxV2CampaignServiceBriefsUpdateCampaignBearerTokenFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("bearer-token", "", "") + lfxV2CampaignServiceBriefsUpdateCampaignIfMatchFlag = lfxV2CampaignServiceBriefsUpdateCampaignFlags.String("if-match", "", "") + + lfxV2CampaignServiceBriefsGetJobFlags = flag.NewFlagSet("get-job", flag.ExitOnError) + lfxV2CampaignServiceBriefsGetJobProjectIDFlag = lfxV2CampaignServiceBriefsGetJobFlags.String("project-id", "REQUIRED", "Project UUID or slug that scopes the connection") + lfxV2CampaignServiceBriefsGetJobJobIDFlag = lfxV2CampaignServiceBriefsGetJobFlags.String("job-id", "REQUIRED", "Job UUID") + lfxV2CampaignServiceBriefsGetJobBearerTokenFlag = lfxV2CampaignServiceBriefsGetJobFlags.String("bearer-token", "", "") + lfxV2CampaignServiceConnectionsFlags = flag.NewFlagSet("lfx-v2-campaign-service-connections", flag.ContinueOnError) lfxV2CampaignServiceConnectionsCreateGoogleAdsFlags = flag.NewFlagSet("create-google-ads", flag.ExitOnError) @@ -250,6 +308,17 @@ func ParseEndpoint( lfxV2CampaignServiceSvcLivezFlags = flag.NewFlagSet("livez", flag.ExitOnError) ) + lfxV2CampaignServiceBriefsFlags.Usage = lfxV2CampaignServiceBriefsUsage + lfxV2CampaignServiceBriefsCreateBriefFlags.Usage = lfxV2CampaignServiceBriefsCreateBriefUsage + lfxV2CampaignServiceBriefsGetBriefFlags.Usage = lfxV2CampaignServiceBriefsGetBriefUsage + lfxV2CampaignServiceBriefsUpdateBriefFlags.Usage = lfxV2CampaignServiceBriefsUpdateBriefUsage + lfxV2CampaignServiceBriefsApproveBriefFlags.Usage = lfxV2CampaignServiceBriefsApproveBriefUsage + lfxV2CampaignServiceBriefsDeleteBriefFlags.Usage = lfxV2CampaignServiceBriefsDeleteBriefUsage + lfxV2CampaignServiceBriefsCreateCampaignsFlags.Usage = lfxV2CampaignServiceBriefsCreateCampaignsUsage + lfxV2CampaignServiceBriefsGetCampaignFlags.Usage = lfxV2CampaignServiceBriefsGetCampaignUsage + lfxV2CampaignServiceBriefsUpdateCampaignFlags.Usage = lfxV2CampaignServiceBriefsUpdateCampaignUsage + lfxV2CampaignServiceBriefsGetJobFlags.Usage = lfxV2CampaignServiceBriefsGetJobUsage + lfxV2CampaignServiceConnectionsFlags.Usage = lfxV2CampaignServiceConnectionsUsage lfxV2CampaignServiceConnectionsCreateGoogleAdsFlags.Usage = lfxV2CampaignServiceConnectionsCreateGoogleAdsUsage lfxV2CampaignServiceConnectionsGetGoogleAdsFlags.Usage = lfxV2CampaignServiceConnectionsGetGoogleAdsUsage @@ -313,6 +382,8 @@ func ParseEndpoint( { svcn = flag.Arg(0) switch svcn { + case "lfx-v2-campaign-service-briefs": + svcf = lfxV2CampaignServiceBriefsFlags case "lfx-v2-campaign-service-connections": svcf = lfxV2CampaignServiceConnectionsFlags case "lfx-v2-campaign-service-svc": @@ -332,6 +403,37 @@ func ParseEndpoint( { epn = svcf.Arg(0) switch svcn { + case "lfx-v2-campaign-service-briefs": + switch epn { + case "create-brief": + epf = lfxV2CampaignServiceBriefsCreateBriefFlags + + case "get-brief": + epf = lfxV2CampaignServiceBriefsGetBriefFlags + + case "update-brief": + epf = lfxV2CampaignServiceBriefsUpdateBriefFlags + + case "approve-brief": + epf = lfxV2CampaignServiceBriefsApproveBriefFlags + + case "delete-brief": + epf = lfxV2CampaignServiceBriefsDeleteBriefFlags + + case "create-campaigns": + epf = lfxV2CampaignServiceBriefsCreateCampaignsFlags + + case "get-campaign": + epf = lfxV2CampaignServiceBriefsGetCampaignFlags + + case "update-campaign": + epf = lfxV2CampaignServiceBriefsUpdateCampaignFlags + + case "get-job": + epf = lfxV2CampaignServiceBriefsGetJobFlags + + } + case "lfx-v2-campaign-service-connections": switch epn { case "create-google-ads": @@ -492,6 +594,37 @@ func ParseEndpoint( ) { switch svcn { + case "lfx-v2-campaign-service-briefs": + c := lfxv2campaignservicebriefsc.NewClient(scheme, host, doer, enc, dec, restore) + switch epn { + case "create-brief": + endpoint = c.CreateBrief() + data, err = lfxv2campaignservicebriefsc.BuildCreateBriefPayload(*lfxV2CampaignServiceBriefsCreateBriefBodyFlag, *lfxV2CampaignServiceBriefsCreateBriefProjectIDFlag, *lfxV2CampaignServiceBriefsCreateBriefBearerTokenFlag) + case "get-brief": + endpoint = c.GetBrief() + data, err = lfxv2campaignservicebriefsc.BuildGetBriefPayload(*lfxV2CampaignServiceBriefsGetBriefProjectIDFlag, *lfxV2CampaignServiceBriefsGetBriefBriefIDFlag, *lfxV2CampaignServiceBriefsGetBriefBearerTokenFlag) + case "update-brief": + endpoint = c.UpdateBrief() + data, err = lfxv2campaignservicebriefsc.BuildUpdateBriefPayload(*lfxV2CampaignServiceBriefsUpdateBriefBodyFlag, *lfxV2CampaignServiceBriefsUpdateBriefProjectIDFlag, *lfxV2CampaignServiceBriefsUpdateBriefBriefIDFlag, *lfxV2CampaignServiceBriefsUpdateBriefBearerTokenFlag, *lfxV2CampaignServiceBriefsUpdateBriefIfMatchFlag) + case "approve-brief": + endpoint = c.ApproveBrief() + data, err = lfxv2campaignservicebriefsc.BuildApproveBriefPayload(*lfxV2CampaignServiceBriefsApproveBriefProjectIDFlag, *lfxV2CampaignServiceBriefsApproveBriefBriefIDFlag, *lfxV2CampaignServiceBriefsApproveBriefBearerTokenFlag, *lfxV2CampaignServiceBriefsApproveBriefIfMatchFlag) + case "delete-brief": + endpoint = c.DeleteBrief() + data, err = lfxv2campaignservicebriefsc.BuildDeleteBriefPayload(*lfxV2CampaignServiceBriefsDeleteBriefProjectIDFlag, *lfxV2CampaignServiceBriefsDeleteBriefBriefIDFlag, *lfxV2CampaignServiceBriefsDeleteBriefBearerTokenFlag) + case "create-campaigns": + endpoint = c.CreateCampaigns() + data, err = lfxv2campaignservicebriefsc.BuildCreateCampaignsPayload(*lfxV2CampaignServiceBriefsCreateCampaignsBodyFlag, *lfxV2CampaignServiceBriefsCreateCampaignsProjectIDFlag, *lfxV2CampaignServiceBriefsCreateCampaignsBriefIDFlag, *lfxV2CampaignServiceBriefsCreateCampaignsBearerTokenFlag) + case "get-campaign": + endpoint = c.GetCampaign() + data, err = lfxv2campaignservicebriefsc.BuildGetCampaignPayload(*lfxV2CampaignServiceBriefsGetCampaignProjectIDFlag, *lfxV2CampaignServiceBriefsGetCampaignBriefIDFlag, *lfxV2CampaignServiceBriefsGetCampaignCampaignIDFlag, *lfxV2CampaignServiceBriefsGetCampaignBearerTokenFlag) + case "update-campaign": + endpoint = c.UpdateCampaign() + data, err = lfxv2campaignservicebriefsc.BuildUpdateCampaignPayload(*lfxV2CampaignServiceBriefsUpdateCampaignBodyFlag, *lfxV2CampaignServiceBriefsUpdateCampaignProjectIDFlag, *lfxV2CampaignServiceBriefsUpdateCampaignBriefIDFlag, *lfxV2CampaignServiceBriefsUpdateCampaignCampaignIDFlag, *lfxV2CampaignServiceBriefsUpdateCampaignBearerTokenFlag, *lfxV2CampaignServiceBriefsUpdateCampaignIfMatchFlag) + case "get-job": + endpoint = c.GetJob() + data, err = lfxv2campaignservicebriefsc.BuildGetJobPayload(*lfxV2CampaignServiceBriefsGetJobProjectIDFlag, *lfxV2CampaignServiceBriefsGetJobJobIDFlag, *lfxV2CampaignServiceBriefsGetJobBearerTokenFlag) + } case "lfx-v2-campaign-service-connections": c := lfxv2campaignserviceconnectionsc.NewClient(scheme, host, doer, enc, dec, restore) switch epn { @@ -639,6 +772,239 @@ func ParseEndpoint( return endpoint, data, nil } +// lfxV2CampaignServiceBriefsUsage displays the usage of the +// lfx-v2-campaign-service-briefs command and its subcommands. +func lfxV2CampaignServiceBriefsUsage() { + fmt.Fprintln(os.Stderr, `Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation.`) + fmt.Fprintf(os.Stderr, "Usage:\n %s [globalflags] lfx-v2-campaign-service-briefs COMMAND [flags]\n\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "COMMAND:") + fmt.Fprintln(os.Stderr, ` create-brief: Create a brief.`) + fmt.Fprintln(os.Stderr, ` get-brief: Get a brief; returns ETag.`) + fmt.Fprintln(os.Stderr, ` update-brief: Replace a brief (requires If-Match).`) + fmt.Fprintln(os.Stderr, ` approve-brief: Approve a brief for campaign creation.`) + fmt.Fprintln(os.Stderr, ` delete-brief: Archive a brief (soft delete).`) + fmt.Fprintln(os.Stderr, ` create-campaigns: Create campaigns across the selected platforms (async -> job).`) + fmt.Fprintln(os.Stderr, ` get-campaign: Get one campaign under a brief; returns ETag.`) + fmt.Fprintln(os.Stderr, ` update-campaign: Replace a campaign (requires If-Match).`) + fmt.Fprintln(os.Stderr, ` get-job: Poll campaign-creation job status.`) + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Additional help:") + fmt.Fprintf(os.Stderr, " %s lfx-v2-campaign-service-briefs COMMAND --help\n", os.Args[0]) +} +func lfxV2CampaignServiceBriefsCreateBriefUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs create-brief", os.Args[0]) + fmt.Fprint(os.Stderr, " -body JSON") + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Create a brief.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -body JSON: `) + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs create-brief --body '{\n \"brief\": {\n \"copy\": \"Autem veniam.\",\n \"event_details\": \"Vel autem voluptates.\",\n \"event_slug\": \"Et est laboriosam.\",\n \"keywords\": \"Enim unde omnis.\",\n \"platforms\": [\n \"Nam exercitationem consectetur sint.\",\n \"Necessitatibus ex ea ullam corrupti.\",\n \"Et omnis accusantium.\",\n \"Iure modi asperiores ut.\"\n ],\n \"program_type\": \"education\",\n \"targeting\": \"Quam sed rem quia consequuntur ut.\",\n \"url\": \"Facere sint totam sapiente vitae numquam ut.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") +} + +func lfxV2CampaignServiceBriefsGetBriefUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs get-brief", os.Args[0]) + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Get a brief; returns ETag.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs get-brief --project-id \"cncf\" --brief-id \"b1b28eb8-9957-4b18-8f64-00e2fb848653\" --bearer-token \"eyJhbGci...\"") +} + +func lfxV2CampaignServiceBriefsUpdateBriefUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs update-brief", os.Args[0]) + fmt.Fprint(os.Stderr, " -body JSON") + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprint(os.Stderr, " -if-match STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Replace a brief (requires If-Match).`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -body JSON: `) + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + fmt.Fprintln(os.Stderr, ` -if-match STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs update-brief --body '{\n \"brief\": {\n \"copy\": \"Autem veniam.\",\n \"event_details\": \"Vel autem voluptates.\",\n \"event_slug\": \"Et est laboriosam.\",\n \"keywords\": \"Enim unde omnis.\",\n \"platforms\": [\n \"Nam exercitationem consectetur sint.\",\n \"Necessitatibus ex ea ullam corrupti.\",\n \"Et omnis accusantium.\",\n \"Iure modi asperiores ut.\"\n ],\n \"program_type\": \"education\",\n \"targeting\": \"Quam sed rem quia consequuntur ut.\",\n \"url\": \"Facere sint totam sapiente vitae numquam ut.\"\n }\n }' --project-id \"cncf\" --brief-id \"30229466-ae87-47ac-b7d5-b53993dbed1c\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") +} + +func lfxV2CampaignServiceBriefsApproveBriefUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs approve-brief", os.Args[0]) + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprint(os.Stderr, " -if-match STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Approve a brief for campaign creation.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + fmt.Fprintln(os.Stderr, ` -if-match STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs approve-brief --project-id \"cncf\" --brief-id \"2c2e19a1-5233-40e5-bbb4-66a813f98d6a\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") +} + +func lfxV2CampaignServiceBriefsDeleteBriefUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs delete-brief", os.Args[0]) + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Archive a brief (soft delete).`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs delete-brief --project-id \"cncf\" --brief-id \"4d1db8e4-22e4-44c5-87f5-a4ec939571cf\" --bearer-token \"eyJhbGci...\"") +} + +func lfxV2CampaignServiceBriefsCreateCampaignsUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs create-campaigns", os.Args[0]) + fmt.Fprint(os.Stderr, " -body JSON") + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Create campaigns across the selected platforms (async -> job).`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -body JSON: `) + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs create-campaigns --body '{\n \"input\": {\n \"config\": \"Quia quos ut tempora consequuntur cumque repudiandae.\",\n \"platforms\": [\n \"microsoft-ads\",\n \"microsoft-ads\",\n \"linkedin-ads\"\n ]\n }\n }' --project-id \"cncf\" --brief-id \"c19b94cb-dfbb-4eb2-a345-6868fe5cb0d9\" --bearer-token \"eyJhbGci...\"") +} + +func lfxV2CampaignServiceBriefsGetCampaignUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs get-campaign", os.Args[0]) + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -campaign-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Get one campaign under a brief; returns ETag.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -campaign-id STRING: Campaign UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs get-campaign --project-id \"cncf\" --brief-id \"fb037a6e-7167-4da7-81bf-1adf39805491\" --campaign-id \"89fe4562-ff49-4584-a8cd-10412d5b4ee5\" --bearer-token \"eyJhbGci...\"") +} + +func lfxV2CampaignServiceBriefsUpdateCampaignUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs update-campaign", os.Args[0]) + fmt.Fprint(os.Stderr, " -body JSON") + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -brief-id STRING") + fmt.Fprint(os.Stderr, " -campaign-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprint(os.Stderr, " -if-match STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Replace a campaign (requires If-Match).`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -body JSON: `) + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -brief-id STRING: Brief UUID`) + fmt.Fprintln(os.Stderr, ` -campaign-id STRING: Campaign UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + fmt.Fprintln(os.Stderr, ` -if-match STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs update-campaign --body '{\n \"campaign\": {\n \"campaign_name\": \"Eos doloribus autem corrupti sed voluptatum laborum.\",\n \"config\": \"Perferendis qui maiores aut reiciendis.\",\n \"status\": \"Suscipit quas sunt eos nulla.\"\n }\n }' --project-id \"cncf\" --brief-id \"8cd37251-f650-4b5c-98ba-af310dab9f72\" --campaign-id \"c8fd554b-b3a8-496c-b2a6-0b0e028ba181\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") +} + +func lfxV2CampaignServiceBriefsGetJobUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] lfx-v2-campaign-service-briefs get-job", os.Args[0]) + fmt.Fprint(os.Stderr, " -project-id STRING") + fmt.Fprint(os.Stderr, " -job-id STRING") + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `Poll campaign-creation job status.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -project-id STRING: Project UUID or slug that scopes the connection`) + fmt.Fprintln(os.Stderr, ` -job-id STRING: Job UUID`) + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-briefs get-job --project-id \"cncf\" --job-id \"9f3a62f8-d075-4c8e-8542-bc3b708ab09e\" --bearer-token \"eyJhbGci...\"") +} + // lfxV2CampaignServiceConnectionsUsage displays the usage of the // lfx-v2-campaign-service-connections command and its subcommands. func lfxV2CampaignServiceConnectionsUsage() { @@ -710,7 +1076,7 @@ func lfxV2CampaignServiceConnectionsCreateGoogleAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-google-ads --body '{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Recusandae nam autem dicta perspiciatis quod.\",\n \"client_secret\": \"Quo voluptatem quas culpa omnis consequuntur iste.\",\n \"developer_token\": \"Id magnam id nihil fugiat.\",\n \"refresh_token\": \"Inventore quisquam laudantium velit.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-google-ads --body '{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Architecto itaque doloribus voluptatem consequatur quis fugiat.\",\n \"client_secret\": \"Recusandae qui culpa.\",\n \"developer_token\": \"Accusamus et atque enim.\",\n \"refresh_token\": \"Dolorum minus voluptatem.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetGoogleAdsUsage() { @@ -816,7 +1182,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialGoogleAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-google-ads --body '{\n \"credentials\": {\n \"client_id\": \"Recusandae nam autem dicta perspiciatis quod.\",\n \"client_secret\": \"Quo voluptatem quas culpa omnis consequuntur iste.\",\n \"developer_token\": \"Id magnam id nihil fugiat.\",\n \"refresh_token\": \"Inventore quisquam laudantium velit.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-google-ads --body '{\n \"credentials\": {\n \"client_id\": \"Architecto itaque doloribus voluptatem consequatur quis fugiat.\",\n \"client_secret\": \"Recusandae qui culpa.\",\n \"developer_token\": \"Accusamus et atque enim.\",\n \"refresh_token\": \"Dolorum minus voluptatem.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateLinkedinAdsUsage() { @@ -838,7 +1204,7 @@ func lfxV2CampaignServiceConnectionsCreateLinkedinAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-linkedin-ads --body '{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Fugit voluptatem.\",\n \"org_id\": \"208777\"\n },\n \"credentials\": {\n \"access_token\": \"Amet necessitatibus corrupti quaerat molestiae maiores.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-linkedin-ads --body '{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Temporibus et provident maiores neque.\",\n \"org_id\": \"208777\"\n },\n \"credentials\": {\n \"access_token\": \"Illo minima eaque earum.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetLinkedinAdsUsage() { @@ -882,7 +1248,7 @@ func lfxV2CampaignServiceConnectionsUpdateLinkedinAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-linkedin-ads --body '{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Fugit voluptatem.\",\n \"org_id\": \"208777\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-linkedin-ads --body '{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Temporibus et provident maiores neque.\",\n \"org_id\": \"208777\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteLinkedinAdsUsage() { @@ -944,7 +1310,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-linkedin-ads --body '{\n \"credentials\": {\n \"access_token\": \"Amet necessitatibus corrupti quaerat molestiae maiores.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-linkedin-ads --body '{\n \"credentials\": {\n \"access_token\": \"Illo minima eaque earum.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateMetaAdsUsage() { @@ -966,7 +1332,7 @@ func lfxV2CampaignServiceConnectionsCreateMetaAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-meta-ads --body '{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Eligendi vero temporibus.\",\n \"label\": \"Non maiores quae sed quia.\",\n \"page_id\": \"Facilis eum ipsam.\"\n },\n \"credentials\": {\n \"access_token\": \"Soluta quas voluptas minus possimus quos praesentium.\",\n \"app_secret\": \"Repudiandae consequatur fuga dolores.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-meta-ads --body '{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Qui rerum est sed sed.\",\n \"label\": \"Quis quam dolor occaecati earum iusto rerum.\",\n \"page_id\": \"Cupiditate nobis.\"\n },\n \"credentials\": {\n \"access_token\": \"Enim ea.\",\n \"app_secret\": \"Quidem molestiae deleniti omnis.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetMetaAdsUsage() { @@ -1010,7 +1376,7 @@ func lfxV2CampaignServiceConnectionsUpdateMetaAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-meta-ads --body '{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Eligendi vero temporibus.\",\n \"label\": \"Non maiores quae sed quia.\",\n \"page_id\": \"Facilis eum ipsam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-meta-ads --body '{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Qui rerum est sed sed.\",\n \"label\": \"Quis quam dolor occaecati earum iusto rerum.\",\n \"page_id\": \"Cupiditate nobis.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteMetaAdsUsage() { @@ -1072,7 +1438,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialMetaAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-meta-ads --body '{\n \"credentials\": {\n \"access_token\": \"Soluta quas voluptas minus possimus quos praesentium.\",\n \"app_secret\": \"Repudiandae consequatur fuga dolores.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-meta-ads --body '{\n \"credentials\": {\n \"access_token\": \"Enim ea.\",\n \"app_secret\": \"Quidem molestiae deleniti omnis.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateRedditAdsUsage() { @@ -1094,7 +1460,7 @@ func lfxV2CampaignServiceConnectionsCreateRedditAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-reddit-ads --body '{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Ut est nobis aspernatur.\"\n },\n \"credentials\": {\n \"client_id\": \"Fugit qui tempora commodi reiciendis sit alias.\",\n \"client_secret\": \"Ea enim quidem odio et sequi consequatur.\",\n \"refresh_token\": \"Provident itaque.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-reddit-ads --body '{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Laboriosam qui id enim.\"\n },\n \"credentials\": {\n \"client_id\": \"Magni repudiandae.\",\n \"client_secret\": \"Nihil corrupti repellat eius.\",\n \"refresh_token\": \"Qui nobis et delectus quam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetRedditAdsUsage() { @@ -1138,7 +1504,7 @@ func lfxV2CampaignServiceConnectionsUpdateRedditAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-reddit-ads --body '{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Ut est nobis aspernatur.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-reddit-ads --body '{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Laboriosam qui id enim.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteRedditAdsUsage() { @@ -1200,7 +1566,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialRedditAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-reddit-ads --body '{\n \"credentials\": {\n \"client_id\": \"Fugit qui tempora commodi reiciendis sit alias.\",\n \"client_secret\": \"Ea enim quidem odio et sequi consequatur.\",\n \"refresh_token\": \"Provident itaque.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-reddit-ads --body '{\n \"credentials\": {\n \"client_id\": \"Magni repudiandae.\",\n \"client_secret\": \"Nihil corrupti repellat eius.\",\n \"refresh_token\": \"Qui nobis et delectus quam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateTwitterAdsUsage() { @@ -1222,7 +1588,7 @@ func lfxV2CampaignServiceConnectionsCreateTwitterAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-twitter-ads --body '{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Eveniet dicta numquam doloremque.\",\n \"label\": \"Ea velit et assumenda recusandae enim.\"\n },\n \"credentials\": {\n \"access_token\": \"Ipsum quia ad a.\",\n \"access_token_secret\": \"Veritatis omnis excepturi.\",\n \"consumer_key\": \"Cumque libero.\",\n \"consumer_secret\": \"Id incidunt qui et ratione sit quaerat.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-twitter-ads --body '{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Dolorem non laudantium sunt minima repudiandae illo.\",\n \"label\": \"Labore esse.\"\n },\n \"credentials\": {\n \"access_token\": \"Deleniti placeat fuga aut saepe.\",\n \"access_token_secret\": \"Reiciendis pariatur repellat.\",\n \"consumer_key\": \"Nulla dolorum deserunt ut sit dolor.\",\n \"consumer_secret\": \"Eos nihil ipsam ex odio totam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetTwitterAdsUsage() { @@ -1266,7 +1632,7 @@ func lfxV2CampaignServiceConnectionsUpdateTwitterAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-twitter-ads --body '{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Eveniet dicta numquam doloremque.\",\n \"label\": \"Ea velit et assumenda recusandae enim.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-twitter-ads --body '{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Dolorem non laudantium sunt minima repudiandae illo.\",\n \"label\": \"Labore esse.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteTwitterAdsUsage() { @@ -1328,7 +1694,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialTwitterAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-twitter-ads --body '{\n \"credentials\": {\n \"access_token\": \"Ipsum quia ad a.\",\n \"access_token_secret\": \"Veritatis omnis excepturi.\",\n \"consumer_key\": \"Cumque libero.\",\n \"consumer_secret\": \"Id incidunt qui et ratione sit quaerat.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-twitter-ads --body '{\n \"credentials\": {\n \"access_token\": \"Deleniti placeat fuga aut saepe.\",\n \"access_token_secret\": \"Reiciendis pariatur repellat.\",\n \"consumer_key\": \"Nulla dolorum deserunt ut sit dolor.\",\n \"consumer_secret\": \"Eos nihil ipsam ex odio totam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateMicrosoftAdsUsage() { @@ -1350,7 +1716,7 @@ func lfxV2CampaignServiceConnectionsCreateMicrosoftAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-microsoft-ads --body '{\n \"config\": {\n \"account_id\": \"Sint qui praesentium ea praesentium magni.\",\n \"customer_id\": \"Iure dolore et et aut nam.\",\n \"label\": \"Consequuntur neque mollitia quia.\"\n },\n \"credentials\": {\n \"client_id\": \"Voluptates odit consequatur quis.\",\n \"client_secret\": \"Est voluptatum enim earum et doloribus.\",\n \"developer_token\": \"Id ducimus eius et laboriosam omnis vitae.\",\n \"refresh_token\": \"Ea consequatur et ut.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-microsoft-ads --body '{\n \"config\": {\n \"account_id\": \"Ut necessitatibus dolor nemo.\",\n \"customer_id\": \"Nisi numquam nihil id unde.\",\n \"label\": \"Voluptatem sed illo numquam praesentium tenetur quas.\"\n },\n \"credentials\": {\n \"client_id\": \"Officiis aut fugiat optio ut.\",\n \"client_secret\": \"Et voluptas tenetur nobis facere.\",\n \"developer_token\": \"Blanditiis mollitia dolores illo nostrum magnam.\",\n \"refresh_token\": \"Quo corporis recusandae dicta quibusdam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetMicrosoftAdsUsage() { @@ -1394,7 +1760,7 @@ func lfxV2CampaignServiceConnectionsUpdateMicrosoftAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-microsoft-ads --body '{\n \"config\": {\n \"account_id\": \"Sint qui praesentium ea praesentium magni.\",\n \"customer_id\": \"Iure dolore et et aut nam.\",\n \"label\": \"Consequuntur neque mollitia quia.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-microsoft-ads --body '{\n \"config\": {\n \"account_id\": \"Ut necessitatibus dolor nemo.\",\n \"customer_id\": \"Nisi numquam nihil id unde.\",\n \"label\": \"Voluptatem sed illo numquam praesentium tenetur quas.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteMicrosoftAdsUsage() { @@ -1456,7 +1822,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-microsoft-ads --body '{\n \"credentials\": {\n \"client_id\": \"Voluptates odit consequatur quis.\",\n \"client_secret\": \"Est voluptatum enim earum et doloribus.\",\n \"developer_token\": \"Id ducimus eius et laboriosam omnis vitae.\",\n \"refresh_token\": \"Ea consequatur et ut.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-microsoft-ads --body '{\n \"credentials\": {\n \"client_id\": \"Officiis aut fugiat optio ut.\",\n \"client_secret\": \"Et voluptas tenetur nobis facere.\",\n \"developer_token\": \"Blanditiis mollitia dolores illo nostrum magnam.\",\n \"refresh_token\": \"Quo corporis recusandae dicta quibusdam.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsCreateHubspotUsage() { @@ -1478,7 +1844,7 @@ func lfxV2CampaignServiceConnectionsCreateHubspotUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-hubspot --body '{\n \"config\": {\n \"account_id\": \"Temporibus voluptas inventore sit omnis.\",\n \"brand_kit\": \"Quis facere sint totam sapiente.\",\n \"label\": \"Sint nihil.\",\n \"portal_id\": \"Expedita veniam beatae autem.\",\n \"sender_email\": \"Placeat enim.\",\n \"sender_name\": \"Nesciunt asperiores et est.\"\n },\n \"credentials\": {\n \"private_app_token\": \"Numquam ut nesciunt voluptas.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections create-hubspot --body '{\n \"config\": {\n \"account_id\": \"Maxime dicta qui aut.\",\n \"brand_kit\": \"Corrupti voluptas.\",\n \"label\": \"Dolor porro inventore alias error.\",\n \"portal_id\": \"Suscipit eos nam ipsa.\",\n \"sender_email\": \"Velit et impedit facilis quaerat.\",\n \"sender_name\": \"Dolor ut velit quam omnis et dolor.\"\n },\n \"credentials\": {\n \"private_app_token\": \"Vitae vel omnis autem accusamus mollitia.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } func lfxV2CampaignServiceConnectionsGetHubspotUsage() { @@ -1522,7 +1888,7 @@ func lfxV2CampaignServiceConnectionsUpdateHubspotUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-hubspot --body '{\n \"config\": {\n \"account_id\": \"Temporibus voluptas inventore sit omnis.\",\n \"brand_kit\": \"Quis facere sint totam sapiente.\",\n \"label\": \"Sint nihil.\",\n \"portal_id\": \"Expedita veniam beatae autem.\",\n \"sender_email\": \"Placeat enim.\",\n \"sender_name\": \"Nesciunt asperiores et est.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections update-hubspot --body '{\n \"config\": {\n \"account_id\": \"Maxime dicta qui aut.\",\n \"brand_kit\": \"Corrupti voluptas.\",\n \"label\": \"Dolor porro inventore alias error.\",\n \"portal_id\": \"Suscipit eos nam ipsa.\",\n \"sender_email\": \"Velit et impedit facilis quaerat.\",\n \"sender_name\": \"Dolor ut velit quam omnis et dolor.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\" --if-match \"3\"") } func lfxV2CampaignServiceConnectionsDeleteHubspotUsage() { @@ -1584,7 +1950,7 @@ func lfxV2CampaignServiceConnectionsSetCredentialHubspotUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-hubspot --body '{\n \"credentials\": {\n \"private_app_token\": \"Numquam ut nesciunt voluptas.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "lfx-v2-campaign-service-connections set-credential-hubspot --body '{\n \"credentials\": {\n \"private_app_token\": \"Vitae vel omnis autem accusamus mollitia.\"\n }\n }' --project-id \"cncf\" --bearer-token \"eyJhbGci...\"") } // lfxV2CampaignServiceSvcUsage displays the usage of the diff --git a/gen/http/lfx_v2_campaign_service_briefs/client/cli.go b/gen/http/lfx_v2_campaign_service_briefs/client/cli.go new file mode 100644 index 00000000..21340ea6 --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/client/cli.go @@ -0,0 +1,395 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP client CLI support package +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package client + +import ( + "encoding/json" + "fmt" + + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goa "goa.design/goa/v3/pkg" +) + +// BuildCreateBriefPayload builds the payload for the +// lfx-v2-campaign-service-briefs create-brief endpoint from CLI flags. +func BuildCreateBriefPayload(lfxV2CampaignServiceBriefsCreateBriefBody string, lfxV2CampaignServiceBriefsCreateBriefProjectID string, lfxV2CampaignServiceBriefsCreateBriefBearerToken string) (*lfxv2campaignservicebriefs.CreateBriefPayload, error) { + var err error + var body CreateBriefRequestBody + { + err = json.Unmarshal([]byte(lfxV2CampaignServiceBriefsCreateBriefBody), &body) + if err != nil { + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"brief\": {\n \"copy\": \"Autem veniam.\",\n \"event_details\": \"Vel autem voluptates.\",\n \"event_slug\": \"Et est laboriosam.\",\n \"keywords\": \"Enim unde omnis.\",\n \"platforms\": [\n \"Nam exercitationem consectetur sint.\",\n \"Necessitatibus ex ea ullam corrupti.\",\n \"Et omnis accusantium.\",\n \"Iure modi asperiores ut.\"\n ],\n \"program_type\": \"education\",\n \"targeting\": \"Quam sed rem quia consequuntur ut.\",\n \"url\": \"Facere sint totam sapiente vitae numquam ut.\"\n }\n }'") + } + if body.Brief == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief", "body")) + } + if body.Brief != nil { + if err2 := ValidateBriefInputRequestBody(body.Brief); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + if err != nil { + return nil, err + } + } + var projectID string + { + projectID = lfxV2CampaignServiceBriefsCreateBriefProjectID + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsCreateBriefBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsCreateBriefBearerToken + } + } + v := &lfxv2campaignservicebriefs.CreateBriefPayload{} + if body.Brief != nil { + v.Brief = marshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(body.Brief) + } + v.ProjectID = projectID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildGetBriefPayload builds the payload for the +// lfx-v2-campaign-service-briefs get-brief endpoint from CLI flags. +func BuildGetBriefPayload(lfxV2CampaignServiceBriefsGetBriefProjectID string, lfxV2CampaignServiceBriefsGetBriefBriefID string, lfxV2CampaignServiceBriefsGetBriefBearerToken string) (*lfxv2campaignservicebriefs.GetBriefPayload, error) { + var err error + var projectID string + { + projectID = lfxV2CampaignServiceBriefsGetBriefProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsGetBriefBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsGetBriefBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsGetBriefBearerToken + } + } + v := &lfxv2campaignservicebriefs.GetBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildUpdateBriefPayload builds the payload for the +// lfx-v2-campaign-service-briefs update-brief endpoint from CLI flags. +func BuildUpdateBriefPayload(lfxV2CampaignServiceBriefsUpdateBriefBody string, lfxV2CampaignServiceBriefsUpdateBriefProjectID string, lfxV2CampaignServiceBriefsUpdateBriefBriefID string, lfxV2CampaignServiceBriefsUpdateBriefBearerToken string, lfxV2CampaignServiceBriefsUpdateBriefIfMatch string) (*lfxv2campaignservicebriefs.UpdateBriefPayload, error) { + var err error + var body UpdateBriefRequestBody + { + err = json.Unmarshal([]byte(lfxV2CampaignServiceBriefsUpdateBriefBody), &body) + if err != nil { + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"brief\": {\n \"copy\": \"Autem veniam.\",\n \"event_details\": \"Vel autem voluptates.\",\n \"event_slug\": \"Et est laboriosam.\",\n \"keywords\": \"Enim unde omnis.\",\n \"platforms\": [\n \"Nam exercitationem consectetur sint.\",\n \"Necessitatibus ex ea ullam corrupti.\",\n \"Et omnis accusantium.\",\n \"Iure modi asperiores ut.\"\n ],\n \"program_type\": \"education\",\n \"targeting\": \"Quam sed rem quia consequuntur ut.\",\n \"url\": \"Facere sint totam sapiente vitae numquam ut.\"\n }\n }'") + } + if body.Brief == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief", "body")) + } + if body.Brief != nil { + if err2 := ValidateBriefInputRequestBody(body.Brief); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + if err != nil { + return nil, err + } + } + var projectID string + { + projectID = lfxV2CampaignServiceBriefsUpdateBriefProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsUpdateBriefBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsUpdateBriefBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsUpdateBriefBearerToken + } + } + var ifMatch *string + { + if lfxV2CampaignServiceBriefsUpdateBriefIfMatch != "" { + ifMatch = &lfxV2CampaignServiceBriefsUpdateBriefIfMatch + } + } + v := &lfxv2campaignservicebriefs.UpdateBriefPayload{} + if body.Brief != nil { + v.Brief = marshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(body.Brief) + } + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v, nil +} + +// BuildApproveBriefPayload builds the payload for the +// lfx-v2-campaign-service-briefs approve-brief endpoint from CLI flags. +func BuildApproveBriefPayload(lfxV2CampaignServiceBriefsApproveBriefProjectID string, lfxV2CampaignServiceBriefsApproveBriefBriefID string, lfxV2CampaignServiceBriefsApproveBriefBearerToken string, lfxV2CampaignServiceBriefsApproveBriefIfMatch string) (*lfxv2campaignservicebriefs.ApproveBriefPayload, error) { + var err error + var projectID string + { + projectID = lfxV2CampaignServiceBriefsApproveBriefProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsApproveBriefBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsApproveBriefBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsApproveBriefBearerToken + } + } + var ifMatch *string + { + if lfxV2CampaignServiceBriefsApproveBriefIfMatch != "" { + ifMatch = &lfxV2CampaignServiceBriefsApproveBriefIfMatch + } + } + v := &lfxv2campaignservicebriefs.ApproveBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v, nil +} + +// BuildDeleteBriefPayload builds the payload for the +// lfx-v2-campaign-service-briefs delete-brief endpoint from CLI flags. +func BuildDeleteBriefPayload(lfxV2CampaignServiceBriefsDeleteBriefProjectID string, lfxV2CampaignServiceBriefsDeleteBriefBriefID string, lfxV2CampaignServiceBriefsDeleteBriefBearerToken string) (*lfxv2campaignservicebriefs.DeleteBriefPayload, error) { + var err error + var projectID string + { + projectID = lfxV2CampaignServiceBriefsDeleteBriefProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsDeleteBriefBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsDeleteBriefBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsDeleteBriefBearerToken + } + } + v := &lfxv2campaignservicebriefs.DeleteBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildCreateCampaignsPayload builds the payload for the +// lfx-v2-campaign-service-briefs create-campaigns endpoint from CLI flags. +func BuildCreateCampaignsPayload(lfxV2CampaignServiceBriefsCreateCampaignsBody string, lfxV2CampaignServiceBriefsCreateCampaignsProjectID string, lfxV2CampaignServiceBriefsCreateCampaignsBriefID string, lfxV2CampaignServiceBriefsCreateCampaignsBearerToken string) (*lfxv2campaignservicebriefs.CreateCampaignsPayload, error) { + var err error + var body CreateCampaignsRequestBody + { + err = json.Unmarshal([]byte(lfxV2CampaignServiceBriefsCreateCampaignsBody), &body) + if err != nil { + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"input\": {\n \"config\": \"Quia quos ut tempora consequuntur cumque repudiandae.\",\n \"platforms\": [\n \"microsoft-ads\",\n \"microsoft-ads\",\n \"linkedin-ads\"\n ]\n }\n }'") + } + if body.Input == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("input", "body")) + } + if body.Input != nil { + if err2 := ValidateCampaignCreateInputRequestBody(body.Input); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + if err != nil { + return nil, err + } + } + var projectID string + { + projectID = lfxV2CampaignServiceBriefsCreateCampaignsProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsCreateCampaignsBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsCreateCampaignsBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsCreateCampaignsBearerToken + } + } + v := &lfxv2campaignservicebriefs.CreateCampaignsPayload{} + if body.Input != nil { + v.Input = marshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput(body.Input) + } + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildGetCampaignPayload builds the payload for the +// lfx-v2-campaign-service-briefs get-campaign endpoint from CLI flags. +func BuildGetCampaignPayload(lfxV2CampaignServiceBriefsGetCampaignProjectID string, lfxV2CampaignServiceBriefsGetCampaignBriefID string, lfxV2CampaignServiceBriefsGetCampaignCampaignID string, lfxV2CampaignServiceBriefsGetCampaignBearerToken string) (*lfxv2campaignservicebriefs.GetCampaignPayload, error) { + var err error + var projectID string + { + projectID = lfxV2CampaignServiceBriefsGetCampaignProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsGetCampaignBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var campaignID string + { + campaignID = lfxV2CampaignServiceBriefsGetCampaignCampaignID + err = goa.MergeErrors(err, goa.ValidateFormat("campaign_id", campaignID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsGetCampaignBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsGetCampaignBearerToken + } + } + v := &lfxv2campaignservicebriefs.GetCampaignPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.CampaignID = campaignID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildUpdateCampaignPayload builds the payload for the +// lfx-v2-campaign-service-briefs update-campaign endpoint from CLI flags. +func BuildUpdateCampaignPayload(lfxV2CampaignServiceBriefsUpdateCampaignBody string, lfxV2CampaignServiceBriefsUpdateCampaignProjectID string, lfxV2CampaignServiceBriefsUpdateCampaignBriefID string, lfxV2CampaignServiceBriefsUpdateCampaignCampaignID string, lfxV2CampaignServiceBriefsUpdateCampaignBearerToken string, lfxV2CampaignServiceBriefsUpdateCampaignIfMatch string) (*lfxv2campaignservicebriefs.UpdateCampaignPayload, error) { + var err error + var body UpdateCampaignRequestBody + { + err = json.Unmarshal([]byte(lfxV2CampaignServiceBriefsUpdateCampaignBody), &body) + if err != nil { + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"campaign\": {\n \"campaign_name\": \"Eos doloribus autem corrupti sed voluptatum laborum.\",\n \"config\": \"Perferendis qui maiores aut reiciendis.\",\n \"status\": \"Suscipit quas sunt eos nulla.\"\n }\n }'") + } + if body.Campaign == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("campaign", "body")) + } + if err != nil { + return nil, err + } + } + var projectID string + { + projectID = lfxV2CampaignServiceBriefsUpdateCampaignProjectID + } + var briefID string + { + briefID = lfxV2CampaignServiceBriefsUpdateCampaignBriefID + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var campaignID string + { + campaignID = lfxV2CampaignServiceBriefsUpdateCampaignCampaignID + err = goa.MergeErrors(err, goa.ValidateFormat("campaign_id", campaignID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsUpdateCampaignBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsUpdateCampaignBearerToken + } + } + var ifMatch *string + { + if lfxV2CampaignServiceBriefsUpdateCampaignIfMatch != "" { + ifMatch = &lfxV2CampaignServiceBriefsUpdateCampaignIfMatch + } + } + v := &lfxv2campaignservicebriefs.UpdateCampaignPayload{} + if body.Campaign != nil { + v.Campaign = marshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput(body.Campaign) + } + v.ProjectID = projectID + v.BriefID = briefID + v.CampaignID = campaignID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v, nil +} + +// BuildGetJobPayload builds the payload for the lfx-v2-campaign-service-briefs +// get-job endpoint from CLI flags. +func BuildGetJobPayload(lfxV2CampaignServiceBriefsGetJobProjectID string, lfxV2CampaignServiceBriefsGetJobJobID string, lfxV2CampaignServiceBriefsGetJobBearerToken string) (*lfxv2campaignservicebriefs.GetJobPayload, error) { + var err error + var projectID string + { + projectID = lfxV2CampaignServiceBriefsGetJobProjectID + } + var jobID string + { + jobID = lfxV2CampaignServiceBriefsGetJobJobID + err = goa.MergeErrors(err, goa.ValidateFormat("job_id", jobID, goa.FormatUUID)) + if err != nil { + return nil, err + } + } + var bearerToken *string + { + if lfxV2CampaignServiceBriefsGetJobBearerToken != "" { + bearerToken = &lfxV2CampaignServiceBriefsGetJobBearerToken + } + } + v := &lfxv2campaignservicebriefs.GetJobPayload{} + v.ProjectID = projectID + v.JobID = jobID + v.BearerToken = bearerToken + + return v, nil +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/client/client.go b/gen/http/lfx_v2_campaign_service_briefs/client/client.go new file mode 100644 index 00000000..5c8d1a80 --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/client/client.go @@ -0,0 +1,308 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs client HTTP transport +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package client + +import ( + "context" + "net/http" + + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// Client lists the lfx-v2-campaign-service-briefs service endpoint HTTP +// clients. +type Client struct { + // CreateBrief Doer is the HTTP client used to make requests to the + // create-brief endpoint. + CreateBriefDoer goahttp.Doer + + // GetBrief Doer is the HTTP client used to make requests to the get-brief + // endpoint. + GetBriefDoer goahttp.Doer + + // UpdateBrief Doer is the HTTP client used to make requests to the + // update-brief endpoint. + UpdateBriefDoer goahttp.Doer + + // ApproveBrief Doer is the HTTP client used to make requests to the + // approve-brief endpoint. + ApproveBriefDoer goahttp.Doer + + // DeleteBrief Doer is the HTTP client used to make requests to the + // delete-brief endpoint. + DeleteBriefDoer goahttp.Doer + + // CreateCampaigns Doer is the HTTP client used to make requests to the + // create-campaigns endpoint. + CreateCampaignsDoer goahttp.Doer + + // GetCampaign Doer is the HTTP client used to make requests to the + // get-campaign endpoint. + GetCampaignDoer goahttp.Doer + + // UpdateCampaign Doer is the HTTP client used to make requests to the + // update-campaign endpoint. + UpdateCampaignDoer goahttp.Doer + + // GetJob Doer is the HTTP client used to make requests to the get-job endpoint. + GetJobDoer goahttp.Doer + + // RestoreResponseBody controls whether the response bodies are reset after + // decoding so they can be read again. + RestoreResponseBody bool + + scheme string + host string + encoder func(*http.Request) goahttp.Encoder + decoder func(*http.Response) goahttp.Decoder +} + +// NewClient instantiates HTTP clients for all the +// lfx-v2-campaign-service-briefs service servers. +func NewClient( + scheme string, + host string, + doer goahttp.Doer, + enc func(*http.Request) goahttp.Encoder, + dec func(*http.Response) goahttp.Decoder, + restoreBody bool, +) *Client { + return &Client{ + CreateBriefDoer: doer, + GetBriefDoer: doer, + UpdateBriefDoer: doer, + ApproveBriefDoer: doer, + DeleteBriefDoer: doer, + CreateCampaignsDoer: doer, + GetCampaignDoer: doer, + UpdateCampaignDoer: doer, + GetJobDoer: doer, + RestoreResponseBody: restoreBody, + scheme: scheme, + host: host, + decoder: dec, + encoder: enc, + } +} + +// CreateBrief returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service create-brief server. +func (c *Client) CreateBrief() goa.Endpoint { + var ( + encodeRequest = EncodeCreateBriefRequest(c.encoder) + decodeResponse = DecodeCreateBriefResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildCreateBriefRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.CreateBriefDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return decodeResponse(resp) + } +} + +// GetBrief returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service get-brief server. +func (c *Client) GetBrief() goa.Endpoint { + var ( + encodeRequest = EncodeGetBriefRequest(c.encoder) + decodeResponse = DecodeGetBriefResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildGetBriefRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.GetBriefDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return decodeResponse(resp) + } +} + +// UpdateBrief returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service update-brief server. +func (c *Client) UpdateBrief() goa.Endpoint { + var ( + encodeRequest = EncodeUpdateBriefRequest(c.encoder) + decodeResponse = DecodeUpdateBriefResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildUpdateBriefRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.UpdateBriefDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return decodeResponse(resp) + } +} + +// ApproveBrief returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service approve-brief server. +func (c *Client) ApproveBrief() goa.Endpoint { + var ( + encodeRequest = EncodeApproveBriefRequest(c.encoder) + decodeResponse = DecodeApproveBriefResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildApproveBriefRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.ApproveBriefDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return decodeResponse(resp) + } +} + +// DeleteBrief returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service delete-brief server. +func (c *Client) DeleteBrief() goa.Endpoint { + var ( + encodeRequest = EncodeDeleteBriefRequest(c.encoder) + decodeResponse = DecodeDeleteBriefResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildDeleteBriefRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.DeleteBriefDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return decodeResponse(resp) + } +} + +// CreateCampaigns returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service create-campaigns server. +func (c *Client) CreateCampaigns() goa.Endpoint { + var ( + encodeRequest = EncodeCreateCampaignsRequest(c.encoder) + decodeResponse = DecodeCreateCampaignsResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildCreateCampaignsRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.CreateCampaignsDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return decodeResponse(resp) + } +} + +// GetCampaign returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service get-campaign server. +func (c *Client) GetCampaign() goa.Endpoint { + var ( + encodeRequest = EncodeGetCampaignRequest(c.encoder) + decodeResponse = DecodeGetCampaignResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildGetCampaignRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.GetCampaignDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return decodeResponse(resp) + } +} + +// UpdateCampaign returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service update-campaign server. +func (c *Client) UpdateCampaign() goa.Endpoint { + var ( + encodeRequest = EncodeUpdateCampaignRequest(c.encoder) + decodeResponse = DecodeUpdateCampaignResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildUpdateCampaignRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.UpdateCampaignDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return decodeResponse(resp) + } +} + +// GetJob returns an endpoint that makes HTTP requests to the +// lfx-v2-campaign-service-briefs service get-job server. +func (c *Client) GetJob() goa.Endpoint { + var ( + encodeRequest = EncodeGetJobRequest(c.encoder) + decodeResponse = DecodeGetJobResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildGetJobRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.GetJobDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return decodeResponse(resp) + } +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/client/encode_decode.go b/gen/http/lfx_v2_campaign_service_briefs/client/encode_decode.go new file mode 100644 index 00000000..c314f15d --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/client/encode_decode.go @@ -0,0 +1,1779 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP client encoders and decoders +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package client + +import ( + "bytes" + "context" + "io" + "net/http" + "net/url" + "strings" + + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goahttp "goa.design/goa/v3/http" +) + +// BuildCreateBriefRequest instantiates a HTTP request object with method and +// path set to call the "lfx-v2-campaign-service-briefs" service "create-brief" +// endpoint +func (c *Client) BuildCreateBriefRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.CreateBriefPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "create-brief", "*lfxv2campaignservicebriefs.CreateBriefPayload", v) + } + projectID = p.ProjectID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateBriefLfxV2CampaignServiceBriefsPath(projectID)} + req, err := http.NewRequest("POST", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "create-brief", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeCreateBriefRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs create-brief server. +func EncodeCreateBriefRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.CreateBriefPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "create-brief", "*lfxv2campaignservicebriefs.CreateBriefPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + body := NewCreateBriefRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil + } +} + +// DecodeCreateBriefResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs create-brief endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeCreateBriefResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeCreateBriefResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusCreated: + var ( + body CreateBriefResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewCreateBriefBriefCreated(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body CreateBriefBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil, NewCreateBriefBadRequest(&body) + case http.StatusConflict: + var ( + body CreateBriefConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil, NewCreateBriefConflict(&body) + case http.StatusServiceUnavailable: + var ( + body CreateBriefServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil, NewCreateBriefServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body CreateBriefInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil, NewCreateBriefInternalServerError(&body) + case http.StatusNotFound: + var ( + body CreateBriefNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + err = ValidateCreateBriefNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-brief", err) + } + return nil, NewCreateBriefNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "create-brief", resp.StatusCode, string(body)) + } + } +} + +// BuildGetBriefRequest instantiates a HTTP request object with method and path +// set to call the "lfx-v2-campaign-service-briefs" service "get-brief" endpoint +func (c *Client) BuildGetBriefRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.GetBriefPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-brief", "*lfxv2campaignservicebriefs.GetBriefPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetBriefLfxV2CampaignServiceBriefsPath(projectID, briefID)} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "get-brief", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeGetBriefRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs get-brief server. +func EncodeGetBriefRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.GetBriefPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-brief", "*lfxv2campaignservicebriefs.GetBriefPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeGetBriefResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs get-brief endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeGetBriefResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeGetBriefResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body GetBriefResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewGetBriefBriefOK(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body GetBriefBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return nil, NewGetBriefBadRequest(&body) + case http.StatusConflict: + var ( + body GetBriefConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return nil, NewGetBriefConflict(&body) + case http.StatusServiceUnavailable: + var ( + body GetBriefServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return nil, NewGetBriefServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body GetBriefInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return nil, NewGetBriefInternalServerError(&body) + case http.StatusNotFound: + var ( + body GetBriefNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + err = ValidateGetBriefNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-brief", err) + } + return nil, NewGetBriefNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "get-brief", resp.StatusCode, string(body)) + } + } +} + +// BuildUpdateBriefRequest instantiates a HTTP request object with method and +// path set to call the "lfx-v2-campaign-service-briefs" service "update-brief" +// endpoint +func (c *Client) BuildUpdateBriefRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.UpdateBriefPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "update-brief", "*lfxv2campaignservicebriefs.UpdateBriefPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateBriefLfxV2CampaignServiceBriefsPath(projectID, briefID)} + req, err := http.NewRequest("PUT", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "update-brief", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeUpdateBriefRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs update-brief server. +func EncodeUpdateBriefRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.UpdateBriefPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "update-brief", "*lfxv2campaignservicebriefs.UpdateBriefPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + if p.IfMatch != nil { + head := *p.IfMatch + req.Header.Set("If-Match", head) + } + body := NewUpdateBriefRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil + } +} + +// DecodeUpdateBriefResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs update-brief endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeUpdateBriefResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - "PreconditionFailed" (type *lfxv2campaignservicebriefs.PreconditionFailedError): http.StatusPreconditionFailed +// - "PreconditionRequired" (type *lfxv2campaignservicebriefs.PreconditionRequiredError): http.StatusPreconditionRequired +// - error: internal error +func DecodeUpdateBriefResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body UpdateBriefResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewUpdateBriefBriefOK(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body UpdateBriefBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefBadRequest(&body) + case http.StatusConflict: + var ( + body UpdateBriefConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefConflict(&body) + case http.StatusServiceUnavailable: + var ( + body UpdateBriefServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body UpdateBriefInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefInternalServerError(&body) + case http.StatusNotFound: + var ( + body UpdateBriefNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefNotFound(&body) + case http.StatusPreconditionFailed: + var ( + body UpdateBriefPreconditionFailedResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefPreconditionFailedResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefPreconditionFailed(&body) + case http.StatusPreconditionRequired: + var ( + body UpdateBriefPreconditionRequiredResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + err = ValidateUpdateBriefPreconditionRequiredResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-brief", err) + } + return nil, NewUpdateBriefPreconditionRequired(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "update-brief", resp.StatusCode, string(body)) + } + } +} + +// BuildApproveBriefRequest instantiates a HTTP request object with method and +// path set to call the "lfx-v2-campaign-service-briefs" service +// "approve-brief" endpoint +func (c *Client) BuildApproveBriefRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.ApproveBriefPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "approve-brief", "*lfxv2campaignservicebriefs.ApproveBriefPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: ApproveBriefLfxV2CampaignServiceBriefsPath(projectID, briefID)} + req, err := http.NewRequest("POST", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "approve-brief", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeApproveBriefRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs approve-brief server. +func EncodeApproveBriefRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.ApproveBriefPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "approve-brief", "*lfxv2campaignservicebriefs.ApproveBriefPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + if p.IfMatch != nil { + head := *p.IfMatch + req.Header.Set("If-Match", head) + } + return nil + } +} + +// DecodeApproveBriefResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs approve-brief endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeApproveBriefResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - "PreconditionFailed" (type *lfxv2campaignservicebriefs.PreconditionFailedError): http.StatusPreconditionFailed +// - "PreconditionRequired" (type *lfxv2campaignservicebriefs.PreconditionRequiredError): http.StatusPreconditionRequired +// - error: internal error +func DecodeApproveBriefResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body ApproveBriefResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewApproveBriefBriefOK(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body ApproveBriefBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefBadRequest(&body) + case http.StatusConflict: + var ( + body ApproveBriefConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefConflict(&body) + case http.StatusServiceUnavailable: + var ( + body ApproveBriefServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body ApproveBriefInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefInternalServerError(&body) + case http.StatusNotFound: + var ( + body ApproveBriefNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefNotFound(&body) + case http.StatusPreconditionFailed: + var ( + body ApproveBriefPreconditionFailedResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefPreconditionFailedResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefPreconditionFailed(&body) + case http.StatusPreconditionRequired: + var ( + body ApproveBriefPreconditionRequiredResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + err = ValidateApproveBriefPreconditionRequiredResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "approve-brief", err) + } + return nil, NewApproveBriefPreconditionRequired(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "approve-brief", resp.StatusCode, string(body)) + } + } +} + +// BuildDeleteBriefRequest instantiates a HTTP request object with method and +// path set to call the "lfx-v2-campaign-service-briefs" service "delete-brief" +// endpoint +func (c *Client) BuildDeleteBriefRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.DeleteBriefPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "delete-brief", "*lfxv2campaignservicebriefs.DeleteBriefPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteBriefLfxV2CampaignServiceBriefsPath(projectID, briefID)} + req, err := http.NewRequest("DELETE", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "delete-brief", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeDeleteBriefRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs delete-brief server. +func EncodeDeleteBriefRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.DeleteBriefPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "delete-brief", "*lfxv2campaignservicebriefs.DeleteBriefPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeDeleteBriefResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs delete-brief endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeDeleteBriefResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeDeleteBriefResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + var ( + body DeleteBriefBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + err = ValidateDeleteBriefBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return nil, NewDeleteBriefBadRequest(&body) + case http.StatusConflict: + var ( + body DeleteBriefConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + err = ValidateDeleteBriefConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return nil, NewDeleteBriefConflict(&body) + case http.StatusServiceUnavailable: + var ( + body DeleteBriefServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + err = ValidateDeleteBriefServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return nil, NewDeleteBriefServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body DeleteBriefInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + err = ValidateDeleteBriefInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return nil, NewDeleteBriefInternalServerError(&body) + case http.StatusNotFound: + var ( + body DeleteBriefNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + err = ValidateDeleteBriefNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "delete-brief", err) + } + return nil, NewDeleteBriefNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "delete-brief", resp.StatusCode, string(body)) + } + } +} + +// BuildCreateCampaignsRequest instantiates a HTTP request object with method +// and path set to call the "lfx-v2-campaign-service-briefs" service +// "create-campaigns" endpoint +func (c *Client) BuildCreateCampaignsRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.CreateCampaignsPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "create-campaigns", "*lfxv2campaignservicebriefs.CreateCampaignsPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateCampaignsLfxV2CampaignServiceBriefsPath(projectID, briefID)} + req, err := http.NewRequest("POST", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "create-campaigns", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeCreateCampaignsRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs create-campaigns server. +func EncodeCreateCampaignsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.CreateCampaignsPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "create-campaigns", "*lfxv2campaignservicebriefs.CreateCampaignsPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + body := NewCreateCampaignsRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil + } +} + +// DecodeCreateCampaignsResponse returns a decoder for responses returned by +// the lfx-v2-campaign-service-briefs create-campaigns endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeCreateCampaignsResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeCreateCampaignsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusAccepted: + var ( + body CreateCampaignsResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + res := NewCreateCampaignsJobCreateResponseAccepted(&body) + return res, nil + case http.StatusBadRequest: + var ( + body CreateCampaignsBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil, NewCreateCampaignsBadRequest(&body) + case http.StatusConflict: + var ( + body CreateCampaignsConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil, NewCreateCampaignsConflict(&body) + case http.StatusServiceUnavailable: + var ( + body CreateCampaignsServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil, NewCreateCampaignsServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body CreateCampaignsInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil, NewCreateCampaignsInternalServerError(&body) + case http.StatusNotFound: + var ( + body CreateCampaignsNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + err = ValidateCreateCampaignsNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "create-campaigns", err) + } + return nil, NewCreateCampaignsNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "create-campaigns", resp.StatusCode, string(body)) + } + } +} + +// BuildGetCampaignRequest instantiates a HTTP request object with method and +// path set to call the "lfx-v2-campaign-service-briefs" service "get-campaign" +// endpoint +func (c *Client) BuildGetCampaignRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + campaignID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.GetCampaignPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-campaign", "*lfxv2campaignservicebriefs.GetCampaignPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + campaignID = p.CampaignID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetCampaignLfxV2CampaignServiceBriefsPath(projectID, briefID, campaignID)} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "get-campaign", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeGetCampaignRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs get-campaign server. +func EncodeGetCampaignRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.GetCampaignPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-campaign", "*lfxv2campaignservicebriefs.GetCampaignPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeGetCampaignResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs get-campaign endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeGetCampaignResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeGetCampaignResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body GetCampaignResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewGetCampaignCampaignOK(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body GetCampaignBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return nil, NewGetCampaignBadRequest(&body) + case http.StatusConflict: + var ( + body GetCampaignConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return nil, NewGetCampaignConflict(&body) + case http.StatusServiceUnavailable: + var ( + body GetCampaignServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return nil, NewGetCampaignServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body GetCampaignInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return nil, NewGetCampaignInternalServerError(&body) + case http.StatusNotFound: + var ( + body GetCampaignNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + err = ValidateGetCampaignNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-campaign", err) + } + return nil, NewGetCampaignNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "get-campaign", resp.StatusCode, string(body)) + } + } +} + +// BuildUpdateCampaignRequest instantiates a HTTP request object with method +// and path set to call the "lfx-v2-campaign-service-briefs" service +// "update-campaign" endpoint +func (c *Client) BuildUpdateCampaignRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + briefID string + campaignID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.UpdateCampaignPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "update-campaign", "*lfxv2campaignservicebriefs.UpdateCampaignPayload", v) + } + projectID = p.ProjectID + briefID = p.BriefID + campaignID = p.CampaignID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateCampaignLfxV2CampaignServiceBriefsPath(projectID, briefID, campaignID)} + req, err := http.NewRequest("PUT", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "update-campaign", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeUpdateCampaignRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs update-campaign server. +func EncodeUpdateCampaignRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.UpdateCampaignPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "update-campaign", "*lfxv2campaignservicebriefs.UpdateCampaignPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + if p.IfMatch != nil { + head := *p.IfMatch + req.Header.Set("If-Match", head) + } + body := NewUpdateCampaignRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil + } +} + +// DecodeUpdateCampaignResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs update-campaign endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeUpdateCampaignResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - "PreconditionFailed" (type *lfxv2campaignservicebriefs.PreconditionFailedError): http.StatusPreconditionFailed +// - "PreconditionRequired" (type *lfxv2campaignservicebriefs.PreconditionRequiredError): http.StatusPreconditionRequired +// - error: internal error +func DecodeUpdateCampaignResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body UpdateCampaignResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + var ( + etag *string + ) + etagRaw := resp.Header.Get("Etag") + if etagRaw != "" { + etag = &etagRaw + } + res := NewUpdateCampaignCampaignOK(&body, etag) + return res, nil + case http.StatusBadRequest: + var ( + body UpdateCampaignBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignBadRequest(&body) + case http.StatusConflict: + var ( + body UpdateCampaignConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignConflict(&body) + case http.StatusServiceUnavailable: + var ( + body UpdateCampaignServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body UpdateCampaignInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignInternalServerError(&body) + case http.StatusNotFound: + var ( + body UpdateCampaignNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignNotFound(&body) + case http.StatusPreconditionFailed: + var ( + body UpdateCampaignPreconditionFailedResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignPreconditionFailedResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignPreconditionFailed(&body) + case http.StatusPreconditionRequired: + var ( + body UpdateCampaignPreconditionRequiredResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + err = ValidateUpdateCampaignPreconditionRequiredResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "update-campaign", err) + } + return nil, NewUpdateCampaignPreconditionRequired(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "update-campaign", resp.StatusCode, string(body)) + } + } +} + +// BuildGetJobRequest instantiates a HTTP request object with method and path +// set to call the "lfx-v2-campaign-service-briefs" service "get-job" endpoint +func (c *Client) BuildGetJobRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + projectID string + jobID string + ) + { + p, ok := v.(*lfxv2campaignservicebriefs.GetJobPayload) + if !ok { + return nil, goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-job", "*lfxv2campaignservicebriefs.GetJobPayload", v) + } + projectID = p.ProjectID + jobID = p.JobID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetJobLfxV2CampaignServiceBriefsPath(projectID, jobID)} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("lfx-v2-campaign-service-briefs", "get-job", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeGetJobRequest returns an encoder for requests sent to the +// lfx-v2-campaign-service-briefs get-job server. +func EncodeGetJobRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*lfxv2campaignservicebriefs.GetJobPayload) + if !ok { + return goahttp.ErrInvalidType("lfx-v2-campaign-service-briefs", "get-job", "*lfxv2campaignservicebriefs.GetJobPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeGetJobResponse returns a decoder for responses returned by the +// lfx-v2-campaign-service-briefs get-job endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeGetJobResponse may return the following errors: +// - "BadRequest" (type *lfxv2campaignservicebriefs.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *lfxv2campaignservicebriefs.ConflictError): http.StatusConflict +// - "ServiceUnavailable" (type *lfxv2campaignservicebriefs.ConnServiceUnavailableError): http.StatusServiceUnavailable +// - "InternalServerError" (type *lfxv2campaignservicebriefs.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *lfxv2campaignservicebriefs.NotFoundError): http.StatusNotFound +// - error: internal error +func DecodeGetJobResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body GetJobResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + res := NewGetJobJobPollResponseOK(&body) + return res, nil + case http.StatusBadRequest: + var ( + body GetJobBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return nil, NewGetJobBadRequest(&body) + case http.StatusConflict: + var ( + body GetJobConflictResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobConflictResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return nil, NewGetJobConflict(&body) + case http.StatusServiceUnavailable: + var ( + body GetJobServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return nil, NewGetJobServiceUnavailable(&body) + case http.StatusInternalServerError: + var ( + body GetJobInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return nil, NewGetJobInternalServerError(&body) + case http.StatusNotFound: + var ( + body GetJobNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("lfx-v2-campaign-service-briefs", "get-job", err) + } + err = ValidateGetJobNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("lfx-v2-campaign-service-briefs", "get-job", err) + } + return nil, NewGetJobNotFound(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("lfx-v2-campaign-service-briefs", "get-job", resp.StatusCode, string(body)) + } + } +} + +// marshalLfxv2campaignservicebriefsBriefInputToBriefInputRequestBody builds a +// value of type *BriefInputRequestBody from a value of type +// *lfxv2campaignservicebriefs.BriefInput. +func marshalLfxv2campaignservicebriefsBriefInputToBriefInputRequestBody(v *lfxv2campaignservicebriefs.BriefInput) *BriefInputRequestBody { + res := &BriefInputRequestBody{ + ProgramType: v.ProgramType, + EventSlug: v.EventSlug, + URL: v.URL, + EventDetails: v.EventDetails, + Copy: v.Copy, + Keywords: v.Keywords, + Targeting: v.Targeting, + } + if v.Platforms != nil { + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + } + + return res +} + +// marshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput builds a +// value of type *lfxv2campaignservicebriefs.BriefInput from a value of type +// *BriefInputRequestBody. +func marshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(v *BriefInputRequestBody) *lfxv2campaignservicebriefs.BriefInput { + res := &lfxv2campaignservicebriefs.BriefInput{ + ProgramType: v.ProgramType, + EventSlug: v.EventSlug, + URL: v.URL, + EventDetails: v.EventDetails, + Copy: v.Copy, + Keywords: v.Keywords, + Targeting: v.Targeting, + } + if v.Platforms != nil { + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + } + + return res +} + +// marshalLfxv2campaignservicebriefsCampaignCreateInputToCampaignCreateInputRequestBody +// builds a value of type *CampaignCreateInputRequestBody from a value of type +// *lfxv2campaignservicebriefs.CampaignCreateInput. +func marshalLfxv2campaignservicebriefsCampaignCreateInputToCampaignCreateInputRequestBody(v *lfxv2campaignservicebriefs.CampaignCreateInput) *CampaignCreateInputRequestBody { + res := &CampaignCreateInputRequestBody{ + Config: v.Config, + } + if v.Platforms != nil { + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + } else { + res.Platforms = []string{} + } + + return res +} + +// marshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput +// builds a value of type *lfxv2campaignservicebriefs.CampaignCreateInput from +// a value of type *CampaignCreateInputRequestBody. +func marshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput(v *CampaignCreateInputRequestBody) *lfxv2campaignservicebriefs.CampaignCreateInput { + res := &lfxv2campaignservicebriefs.CampaignCreateInput{ + Config: v.Config, + } + if v.Platforms != nil { + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + } else { + res.Platforms = []string{} + } + + return res +} + +// marshalLfxv2campaignservicebriefsCampaignUpdateInputToCampaignUpdateInputRequestBody +// builds a value of type *CampaignUpdateInputRequestBody from a value of type +// *lfxv2campaignservicebriefs.CampaignUpdateInput. +func marshalLfxv2campaignservicebriefsCampaignUpdateInputToCampaignUpdateInputRequestBody(v *lfxv2campaignservicebriefs.CampaignUpdateInput) *CampaignUpdateInputRequestBody { + res := &CampaignUpdateInputRequestBody{ + CampaignName: v.CampaignName, + Status: v.Status, + Config: v.Config, + } + + return res +} + +// marshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput +// builds a value of type *lfxv2campaignservicebriefs.CampaignUpdateInput from +// a value of type *CampaignUpdateInputRequestBody. +func marshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput(v *CampaignUpdateInputRequestBody) *lfxv2campaignservicebriefs.CampaignUpdateInput { + res := &lfxv2campaignservicebriefs.CampaignUpdateInput{ + CampaignName: v.CampaignName, + Status: v.Status, + Config: v.Config, + } + + return res +} + +// unmarshalPlatformResultResponseBodyToLfxv2campaignservicebriefsPlatformResult +// builds a value of type *lfxv2campaignservicebriefs.PlatformResult from a +// value of type *PlatformResultResponseBody. +func unmarshalPlatformResultResponseBodyToLfxv2campaignservicebriefsPlatformResult(v *PlatformResultResponseBody) *lfxv2campaignservicebriefs.PlatformResult { + if v == nil { + return nil + } + res := &lfxv2campaignservicebriefs.PlatformResult{ + Platform: *v.Platform, + OK: *v.OK, + CampaignID: v.CampaignID, + Error: v.Error, + } + + return res +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/client/paths.go b/gen/http/lfx_v2_campaign_service_briefs/client/paths.go new file mode 100644 index 00000000..72344e32 --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/client/paths.go @@ -0,0 +1,57 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// HTTP request path constructors for the lfx-v2-campaign-service-briefs service. +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package client + +import ( + "fmt" +) + +// CreateBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service create-brief HTTP endpoint. +func CreateBriefLfxV2CampaignServiceBriefsPath(projectID string) string { + return fmt.Sprintf("/projects/%v/briefs", projectID) +} + +// GetBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-brief HTTP endpoint. +func GetBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// UpdateBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service update-brief HTTP endpoint. +func UpdateBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// ApproveBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service approve-brief HTTP endpoint. +func ApproveBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/approve", projectID, briefID) +} + +// DeleteBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service delete-brief HTTP endpoint. +func DeleteBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// CreateCampaignsLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service create-campaigns HTTP endpoint. +func CreateCampaignsLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns", projectID, briefID) +} + +// GetCampaignLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-campaign HTTP endpoint. +func GetCampaignLfxV2CampaignServiceBriefsPath(projectID string, briefID string, campaignID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns/%v", projectID, briefID, campaignID) +} + +// UpdateCampaignLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service update-campaign HTTP endpoint. +func UpdateCampaignLfxV2CampaignServiceBriefsPath(projectID string, briefID string, campaignID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns/%v", projectID, briefID, campaignID) +} + +// GetJobLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-job HTTP endpoint. +func GetJobLfxV2CampaignServiceBriefsPath(projectID string, jobID string) string { + return fmt.Sprintf("/projects/%v/jobs/%v", projectID, jobID) +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/client/types.go b/gen/http/lfx_v2_campaign_service_briefs/client/types.go new file mode 100644 index 00000000..86607baa --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/client/types.go @@ -0,0 +1,2463 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP client types +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package client + +import ( + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goa "goa.design/goa/v3/pkg" +) + +// CreateBriefRequestBody is the type of the "lfx-v2-campaign-service-briefs" +// service "create-brief" endpoint HTTP request body. +type CreateBriefRequestBody struct { + Brief *BriefInputRequestBody `form:"brief" json:"brief" xml:"brief"` +} + +// UpdateBriefRequestBody is the type of the "lfx-v2-campaign-service-briefs" +// service "update-brief" endpoint HTTP request body. +type UpdateBriefRequestBody struct { + Brief *BriefInputRequestBody `form:"brief" json:"brief" xml:"brief"` +} + +// CreateCampaignsRequestBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// request body. +type CreateCampaignsRequestBody struct { + Input *CampaignCreateInputRequestBody `form:"input" json:"input" xml:"input"` +} + +// UpdateCampaignRequestBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// request body. +type UpdateCampaignRequestBody struct { + Campaign *CampaignUpdateInputRequestBody `form:"campaign" json:"campaign" xml:"campaign"` +} + +// CreateBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "create-brief" endpoint HTTP response body. +type CreateBriefResponseBody struct { + // Brief UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Funnel context + ProgramType *string `form:"program_type,omitempty" json:"program_type,omitempty" xml:"program_type,omitempty"` + // Event/course slug (unique within the project) + EventSlug *string `form:"event_slug,omitempty" json:"event_slug,omitempty" xml:"event_slug,omitempty"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// GetBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-brief" endpoint HTTP response body. +type GetBriefResponseBody struct { + // Brief UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Funnel context + ProgramType *string `form:"program_type,omitempty" json:"program_type,omitempty" xml:"program_type,omitempty"` + // Event/course slug (unique within the project) + EventSlug *string `form:"event_slug,omitempty" json:"event_slug,omitempty" xml:"event_slug,omitempty"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// UpdateBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "update-brief" endpoint HTTP response body. +type UpdateBriefResponseBody struct { + // Brief UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Funnel context + ProgramType *string `form:"program_type,omitempty" json:"program_type,omitempty" xml:"program_type,omitempty"` + // Event/course slug (unique within the project) + EventSlug *string `form:"event_slug,omitempty" json:"event_slug,omitempty" xml:"event_slug,omitempty"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// ApproveBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "approve-brief" endpoint HTTP response body. +type ApproveBriefResponseBody struct { + // Brief UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Funnel context + ProgramType *string `form:"program_type,omitempty" json:"program_type,omitempty" xml:"program_type,omitempty"` + // Event/course slug (unique within the project) + EventSlug *string `form:"event_slug,omitempty" json:"event_slug,omitempty" xml:"event_slug,omitempty"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// CreateCampaignsResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body. +type CreateCampaignsResponseBody struct { + // Poll GET /projects/{projectId}/jobs/{jobId} + JobID *string `form:"job_id,omitempty" json:"job_id,omitempty" xml:"job_id,omitempty"` + // Initial status (always 'queued' on create) + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Platforms this job will create on + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` +} + +// GetCampaignResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-campaign" endpoint HTTP response body. +type GetCampaignResponseBody struct { + // Campaign UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Parent brief + BriefID *string `form:"brief_id,omitempty" json:"brief_id,omitempty" xml:"brief_id,omitempty"` + // Channel + Platform *string `form:"platform,omitempty" json:"platform,omitempty" xml:"platform,omitempty"` + // ID returned by the ad platform + PlatformCampaignID *string `form:"platform_campaign_id,omitempty" json:"platform_campaign_id,omitempty" xml:"platform_campaign_id,omitempty"` + // Campaign name + CampaignName *string `form:"campaign_name,omitempty" json:"campaign_name,omitempty" xml:"campaign_name,omitempty"` + // Campaign status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// UpdateCampaignResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body. +type UpdateCampaignResponseBody struct { + // Campaign UUID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Owning project + ProjectID *string `form:"project_id,omitempty" json:"project_id,omitempty" xml:"project_id,omitempty"` + // Parent brief + BriefID *string `form:"brief_id,omitempty" json:"brief_id,omitempty" xml:"brief_id,omitempty"` + // Channel + Platform *string `form:"platform,omitempty" json:"platform,omitempty" xml:"platform,omitempty"` + // ID returned by the ad platform + PlatformCampaignID *string `form:"platform_campaign_id,omitempty" json:"platform_campaign_id,omitempty" xml:"platform_campaign_id,omitempty"` + // Campaign name + CampaignName *string `form:"campaign_name,omitempty" json:"campaign_name,omitempty" xml:"campaign_name,omitempty"` + // Campaign status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Optimistic-concurrency version + Version *int64 `form:"version,omitempty" json:"version,omitempty" xml:"version,omitempty"` +} + +// GetJobResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-job" endpoint HTTP response body. +type GetJobResponseBody struct { + // Job UUID + JobID *string `form:"job_id,omitempty" json:"job_id,omitempty" xml:"job_id,omitempty"` + // Job status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Per-platform results, written once when the job reaches a terminal state + Result []*PlatformResultResponseBody `form:"result,omitempty" json:"result,omitempty" xml:"result,omitempty"` + // Terminal error, if any + Error *string `form:"error,omitempty" json:"error,omitempty" xml:"error,omitempty"` +} + +// CreateBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "BadRequest" error. +type CreateBriefBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "Conflict" error. +type CreateBriefConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type CreateBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type CreateBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "NotFound" error. +type CreateBriefNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "BadRequest" error. +type GetBriefBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "Conflict" error. +type GetBriefConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "InternalServerError" error. +type GetBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "NotFound" error. +type GetBriefNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "BadRequest" error. +type UpdateBriefBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "Conflict" error. +type UpdateBriefConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type UpdateBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type UpdateBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "NotFound" error. +type UpdateBriefNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "PreconditionFailed" error. +type UpdateBriefPreconditionFailedResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateBriefPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "PreconditionRequired" error. +type UpdateBriefPreconditionRequiredResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "BadRequest" error. +type ApproveBriefBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "Conflict" error. +type ApproveBriefConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type ApproveBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type ApproveBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "NotFound" error. +type ApproveBriefNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "PreconditionFailed" error. +type ApproveBriefPreconditionFailedResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// ApproveBriefPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "PreconditionRequired" error. +type ApproveBriefPreconditionRequiredResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// DeleteBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "BadRequest" error. +type DeleteBriefBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// DeleteBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "Conflict" error. +type DeleteBriefConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// DeleteBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type DeleteBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// DeleteBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type DeleteBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// DeleteBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "NotFound" error. +type DeleteBriefNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateCampaignsBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "BadRequest" error. +type CreateCampaignsBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateCampaignsConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "Conflict" error. +type CreateCampaignsConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateCampaignsServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type CreateCampaignsServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateCampaignsInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "InternalServerError" error. +type CreateCampaignsInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// CreateCampaignsNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "NotFound" error. +type CreateCampaignsNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetCampaignBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "BadRequest" error. +type GetCampaignBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetCampaignConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "Conflict" error. +type GetCampaignConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetCampaignServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type GetCampaignServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetCampaignInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "InternalServerError" error. +type GetCampaignInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetCampaignNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "NotFound" error. +type GetCampaignNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "BadRequest" error. +type UpdateCampaignBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "Conflict" error. +type UpdateCampaignConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type UpdateCampaignServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "InternalServerError" error. +type UpdateCampaignInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "NotFound" error. +type UpdateCampaignNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "PreconditionFailed" error. +type UpdateCampaignPreconditionFailedResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// UpdateCampaignPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "PreconditionRequired" error. +type UpdateCampaignPreconditionRequiredResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetJobBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "BadRequest" error. +type GetJobBadRequestResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetJobConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "Conflict" error. +type GetJobConflictResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetJobServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetJobServiceUnavailableResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetJobInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "InternalServerError" error. +type GetJobInternalServerErrorResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// GetJobNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "NotFound" error. +type GetJobNotFoundResponseBody struct { + // HTTP status code + Code *string `form:"code,omitempty" json:"code,omitempty" xml:"code,omitempty"` + // Error message + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + +// BriefInputRequestBody is used to define fields on request body types. +type BriefInputRequestBody struct { + // Funnel context + ProgramType string `form:"program_type" json:"program_type" xml:"program_type"` + // Event/course slug (unique within the project) + EventSlug string `form:"event_slug" json:"event_slug" xml:"event_slug"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` +} + +// CampaignCreateInputRequestBody is used to define fields on request body +// types. +type CampaignCreateInputRequestBody struct { + // Platforms to create campaigns on (binding selection) + Platforms []string `form:"platforms" json:"platforms" xml:"platforms"` + // Per-platform campaign configuration + Config any `form:"config,omitempty" json:"config,omitempty" xml:"config,omitempty"` +} + +// CampaignUpdateInputRequestBody is used to define fields on request body +// types. +type CampaignUpdateInputRequestBody struct { + // Campaign name + CampaignName string `form:"campaign_name" json:"campaign_name" xml:"campaign_name"` + // Campaign status + Status string `form:"status" json:"status" xml:"status"` + // Campaign configuration snapshot + Config any `form:"config,omitempty" json:"config,omitempty" xml:"config,omitempty"` +} + +// PlatformResultResponseBody is used to define fields on response body types. +type PlatformResultResponseBody struct { + // Platform this result is for + Platform *string `form:"platform,omitempty" json:"platform,omitempty" xml:"platform,omitempty"` + // Whether the campaign was created (or reused) successfully + OK *bool `form:"ok,omitempty" json:"ok,omitempty" xml:"ok,omitempty"` + // Upstream platform campaign id. Present when ok; also set on the specific + // failure where the upstream campaign was created but recording it failed, so + // the orphaned id isn't lost. + CampaignID *string `form:"campaign_id,omitempty" json:"campaign_id,omitempty" xml:"campaign_id,omitempty"` + // Failure reason (present when not ok) + Error *string `form:"error,omitempty" json:"error,omitempty" xml:"error,omitempty"` +} + +// NewCreateBriefRequestBody builds the HTTP request body from the payload of +// the "create-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefRequestBody(p *lfxv2campaignservicebriefs.CreateBriefPayload) *CreateBriefRequestBody { + body := &CreateBriefRequestBody{} + if p.Brief != nil { + body.Brief = marshalLfxv2campaignservicebriefsBriefInputToBriefInputRequestBody(p.Brief) + } + return body +} + +// NewUpdateBriefRequestBody builds the HTTP request body from the payload of +// the "update-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefRequestBody(p *lfxv2campaignservicebriefs.UpdateBriefPayload) *UpdateBriefRequestBody { + body := &UpdateBriefRequestBody{} + if p.Brief != nil { + body.Brief = marshalLfxv2campaignservicebriefsBriefInputToBriefInputRequestBody(p.Brief) + } + return body +} + +// NewCreateCampaignsRequestBody builds the HTTP request body from the payload +// of the "create-campaigns" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewCreateCampaignsRequestBody(p *lfxv2campaignservicebriefs.CreateCampaignsPayload) *CreateCampaignsRequestBody { + body := &CreateCampaignsRequestBody{} + if p.Input != nil { + body.Input = marshalLfxv2campaignservicebriefsCampaignCreateInputToCampaignCreateInputRequestBody(p.Input) + } + return body +} + +// NewUpdateCampaignRequestBody builds the HTTP request body from the payload +// of the "update-campaign" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewUpdateCampaignRequestBody(p *lfxv2campaignservicebriefs.UpdateCampaignPayload) *UpdateCampaignRequestBody { + body := &UpdateCampaignRequestBody{} + if p.Campaign != nil { + body.Campaign = marshalLfxv2campaignservicebriefsCampaignUpdateInputToCampaignUpdateInputRequestBody(p.Campaign) + } + return body +} + +// NewCreateBriefBriefCreated builds a "lfx-v2-campaign-service-briefs" service +// "create-brief" endpoint result from a HTTP "Created" response. +func NewCreateBriefBriefCreated(body *CreateBriefResponseBody, etag *string) *lfxv2campaignservicebriefs.Brief { + v := &lfxv2campaignservicebriefs.Brief{ + ID: *body.ID, + ProjectID: *body.ProjectID, + ProgramType: *body.ProgramType, + EventSlug: *body.EventSlug, + URL: body.URL, + EventDetails: body.EventDetails, + Copy: body.Copy, + Keywords: body.Keywords, + Targeting: body.Targeting, + Status: *body.Status, + Version: *body.Version, + } + if body.Platforms != nil { + v.Platforms = make([]string, len(body.Platforms)) + for i, val := range body.Platforms { + v.Platforms[i] = val + } + } + v.Etag = etag + + return v +} + +// NewCreateBriefBadRequest builds a lfx-v2-campaign-service-briefs service +// create-brief endpoint BadRequest error. +func NewCreateBriefBadRequest(body *CreateBriefBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateBriefConflict builds a lfx-v2-campaign-service-briefs service +// create-brief endpoint Conflict error. +func NewCreateBriefConflict(body *CreateBriefConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateBriefServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service create-brief endpoint ServiceUnavailable error. +func NewCreateBriefServiceUnavailable(body *CreateBriefServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateBriefInternalServerError builds a lfx-v2-campaign-service-briefs +// service create-brief endpoint InternalServerError error. +func NewCreateBriefInternalServerError(body *CreateBriefInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateBriefNotFound builds a lfx-v2-campaign-service-briefs service +// create-brief endpoint NotFound error. +func NewCreateBriefNotFound(body *CreateBriefNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetBriefBriefOK builds a "lfx-v2-campaign-service-briefs" service +// "get-brief" endpoint result from a HTTP "OK" response. +func NewGetBriefBriefOK(body *GetBriefResponseBody, etag *string) *lfxv2campaignservicebriefs.Brief { + v := &lfxv2campaignservicebriefs.Brief{ + ID: *body.ID, + ProjectID: *body.ProjectID, + ProgramType: *body.ProgramType, + EventSlug: *body.EventSlug, + URL: body.URL, + EventDetails: body.EventDetails, + Copy: body.Copy, + Keywords: body.Keywords, + Targeting: body.Targeting, + Status: *body.Status, + Version: *body.Version, + } + if body.Platforms != nil { + v.Platforms = make([]string, len(body.Platforms)) + for i, val := range body.Platforms { + v.Platforms[i] = val + } + } + v.Etag = etag + + return v +} + +// NewGetBriefBadRequest builds a lfx-v2-campaign-service-briefs service +// get-brief endpoint BadRequest error. +func NewGetBriefBadRequest(body *GetBriefBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetBriefConflict builds a lfx-v2-campaign-service-briefs service +// get-brief endpoint Conflict error. +func NewGetBriefConflict(body *GetBriefConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetBriefServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service get-brief endpoint ServiceUnavailable error. +func NewGetBriefServiceUnavailable(body *GetBriefServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetBriefInternalServerError builds a lfx-v2-campaign-service-briefs +// service get-brief endpoint InternalServerError error. +func NewGetBriefInternalServerError(body *GetBriefInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetBriefNotFound builds a lfx-v2-campaign-service-briefs service +// get-brief endpoint NotFound error. +func NewGetBriefNotFound(body *GetBriefNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefBriefOK builds a "lfx-v2-campaign-service-briefs" service +// "update-brief" endpoint result from a HTTP "OK" response. +func NewUpdateBriefBriefOK(body *UpdateBriefResponseBody, etag *string) *lfxv2campaignservicebriefs.Brief { + v := &lfxv2campaignservicebriefs.Brief{ + ID: *body.ID, + ProjectID: *body.ProjectID, + ProgramType: *body.ProgramType, + EventSlug: *body.EventSlug, + URL: body.URL, + EventDetails: body.EventDetails, + Copy: body.Copy, + Keywords: body.Keywords, + Targeting: body.Targeting, + Status: *body.Status, + Version: *body.Version, + } + if body.Platforms != nil { + v.Platforms = make([]string, len(body.Platforms)) + for i, val := range body.Platforms { + v.Platforms[i] = val + } + } + v.Etag = etag + + return v +} + +// NewUpdateBriefBadRequest builds a lfx-v2-campaign-service-briefs service +// update-brief endpoint BadRequest error. +func NewUpdateBriefBadRequest(body *UpdateBriefBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefConflict builds a lfx-v2-campaign-service-briefs service +// update-brief endpoint Conflict error. +func NewUpdateBriefConflict(body *UpdateBriefConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service update-brief endpoint ServiceUnavailable error. +func NewUpdateBriefServiceUnavailable(body *UpdateBriefServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefInternalServerError builds a lfx-v2-campaign-service-briefs +// service update-brief endpoint InternalServerError error. +func NewUpdateBriefInternalServerError(body *UpdateBriefInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefNotFound builds a lfx-v2-campaign-service-briefs service +// update-brief endpoint NotFound error. +func NewUpdateBriefNotFound(body *UpdateBriefNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefPreconditionFailed builds a lfx-v2-campaign-service-briefs +// service update-brief endpoint PreconditionFailed error. +func NewUpdateBriefPreconditionFailed(body *UpdateBriefPreconditionFailedResponseBody) *lfxv2campaignservicebriefs.PreconditionFailedError { + v := &lfxv2campaignservicebriefs.PreconditionFailedError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateBriefPreconditionRequired builds a lfx-v2-campaign-service-briefs +// service update-brief endpoint PreconditionRequired error. +func NewUpdateBriefPreconditionRequired(body *UpdateBriefPreconditionRequiredResponseBody) *lfxv2campaignservicebriefs.PreconditionRequiredError { + v := &lfxv2campaignservicebriefs.PreconditionRequiredError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefBriefOK builds a "lfx-v2-campaign-service-briefs" service +// "approve-brief" endpoint result from a HTTP "OK" response. +func NewApproveBriefBriefOK(body *ApproveBriefResponseBody, etag *string) *lfxv2campaignservicebriefs.Brief { + v := &lfxv2campaignservicebriefs.Brief{ + ID: *body.ID, + ProjectID: *body.ProjectID, + ProgramType: *body.ProgramType, + EventSlug: *body.EventSlug, + URL: body.URL, + EventDetails: body.EventDetails, + Copy: body.Copy, + Keywords: body.Keywords, + Targeting: body.Targeting, + Status: *body.Status, + Version: *body.Version, + } + if body.Platforms != nil { + v.Platforms = make([]string, len(body.Platforms)) + for i, val := range body.Platforms { + v.Platforms[i] = val + } + } + v.Etag = etag + + return v +} + +// NewApproveBriefBadRequest builds a lfx-v2-campaign-service-briefs service +// approve-brief endpoint BadRequest error. +func NewApproveBriefBadRequest(body *ApproveBriefBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefConflict builds a lfx-v2-campaign-service-briefs service +// approve-brief endpoint Conflict error. +func NewApproveBriefConflict(body *ApproveBriefConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service approve-brief endpoint ServiceUnavailable error. +func NewApproveBriefServiceUnavailable(body *ApproveBriefServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefInternalServerError builds a lfx-v2-campaign-service-briefs +// service approve-brief endpoint InternalServerError error. +func NewApproveBriefInternalServerError(body *ApproveBriefInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefNotFound builds a lfx-v2-campaign-service-briefs service +// approve-brief endpoint NotFound error. +func NewApproveBriefNotFound(body *ApproveBriefNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefPreconditionFailed builds a lfx-v2-campaign-service-briefs +// service approve-brief endpoint PreconditionFailed error. +func NewApproveBriefPreconditionFailed(body *ApproveBriefPreconditionFailedResponseBody) *lfxv2campaignservicebriefs.PreconditionFailedError { + v := &lfxv2campaignservicebriefs.PreconditionFailedError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewApproveBriefPreconditionRequired builds a lfx-v2-campaign-service-briefs +// service approve-brief endpoint PreconditionRequired error. +func NewApproveBriefPreconditionRequired(body *ApproveBriefPreconditionRequiredResponseBody) *lfxv2campaignservicebriefs.PreconditionRequiredError { + v := &lfxv2campaignservicebriefs.PreconditionRequiredError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewDeleteBriefBadRequest builds a lfx-v2-campaign-service-briefs service +// delete-brief endpoint BadRequest error. +func NewDeleteBriefBadRequest(body *DeleteBriefBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewDeleteBriefConflict builds a lfx-v2-campaign-service-briefs service +// delete-brief endpoint Conflict error. +func NewDeleteBriefConflict(body *DeleteBriefConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewDeleteBriefServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service delete-brief endpoint ServiceUnavailable error. +func NewDeleteBriefServiceUnavailable(body *DeleteBriefServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewDeleteBriefInternalServerError builds a lfx-v2-campaign-service-briefs +// service delete-brief endpoint InternalServerError error. +func NewDeleteBriefInternalServerError(body *DeleteBriefInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewDeleteBriefNotFound builds a lfx-v2-campaign-service-briefs service +// delete-brief endpoint NotFound error. +func NewDeleteBriefNotFound(body *DeleteBriefNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateCampaignsJobCreateResponseAccepted builds a +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint result +// from a HTTP "Accepted" response. +func NewCreateCampaignsJobCreateResponseAccepted(body *CreateCampaignsResponseBody) *lfxv2campaignservicebriefs.JobCreateResponse { + v := &lfxv2campaignservicebriefs.JobCreateResponse{ + JobID: *body.JobID, + Status: *body.Status, + } + v.Platforms = make([]string, len(body.Platforms)) + for i, val := range body.Platforms { + v.Platforms[i] = val + } + + return v +} + +// NewCreateCampaignsBadRequest builds a lfx-v2-campaign-service-briefs service +// create-campaigns endpoint BadRequest error. +func NewCreateCampaignsBadRequest(body *CreateCampaignsBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateCampaignsConflict builds a lfx-v2-campaign-service-briefs service +// create-campaigns endpoint Conflict error. +func NewCreateCampaignsConflict(body *CreateCampaignsConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateCampaignsServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service create-campaigns endpoint ServiceUnavailable error. +func NewCreateCampaignsServiceUnavailable(body *CreateCampaignsServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateCampaignsInternalServerError builds a +// lfx-v2-campaign-service-briefs service create-campaigns endpoint +// InternalServerError error. +func NewCreateCampaignsInternalServerError(body *CreateCampaignsInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewCreateCampaignsNotFound builds a lfx-v2-campaign-service-briefs service +// create-campaigns endpoint NotFound error. +func NewCreateCampaignsNotFound(body *CreateCampaignsNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetCampaignCampaignOK builds a "lfx-v2-campaign-service-briefs" service +// "get-campaign" endpoint result from a HTTP "OK" response. +func NewGetCampaignCampaignOK(body *GetCampaignResponseBody, etag *string) *lfxv2campaignservicebriefs.Campaign { + v := &lfxv2campaignservicebriefs.Campaign{ + ID: *body.ID, + ProjectID: *body.ProjectID, + BriefID: *body.BriefID, + Platform: *body.Platform, + PlatformCampaignID: body.PlatformCampaignID, + CampaignName: *body.CampaignName, + Status: *body.Status, + Version: *body.Version, + } + v.Etag = etag + + return v +} + +// NewGetCampaignBadRequest builds a lfx-v2-campaign-service-briefs service +// get-campaign endpoint BadRequest error. +func NewGetCampaignBadRequest(body *GetCampaignBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetCampaignConflict builds a lfx-v2-campaign-service-briefs service +// get-campaign endpoint Conflict error. +func NewGetCampaignConflict(body *GetCampaignConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetCampaignServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service get-campaign endpoint ServiceUnavailable error. +func NewGetCampaignServiceUnavailable(body *GetCampaignServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetCampaignInternalServerError builds a lfx-v2-campaign-service-briefs +// service get-campaign endpoint InternalServerError error. +func NewGetCampaignInternalServerError(body *GetCampaignInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetCampaignNotFound builds a lfx-v2-campaign-service-briefs service +// get-campaign endpoint NotFound error. +func NewGetCampaignNotFound(body *GetCampaignNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignCampaignOK builds a "lfx-v2-campaign-service-briefs" +// service "update-campaign" endpoint result from a HTTP "OK" response. +func NewUpdateCampaignCampaignOK(body *UpdateCampaignResponseBody, etag *string) *lfxv2campaignservicebriefs.Campaign { + v := &lfxv2campaignservicebriefs.Campaign{ + ID: *body.ID, + ProjectID: *body.ProjectID, + BriefID: *body.BriefID, + Platform: *body.Platform, + PlatformCampaignID: body.PlatformCampaignID, + CampaignName: *body.CampaignName, + Status: *body.Status, + Version: *body.Version, + } + v.Etag = etag + + return v +} + +// NewUpdateCampaignBadRequest builds a lfx-v2-campaign-service-briefs service +// update-campaign endpoint BadRequest error. +func NewUpdateCampaignBadRequest(body *UpdateCampaignBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignConflict builds a lfx-v2-campaign-service-briefs service +// update-campaign endpoint Conflict error. +func NewUpdateCampaignConflict(body *UpdateCampaignConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignServiceUnavailable builds a lfx-v2-campaign-service-briefs +// service update-campaign endpoint ServiceUnavailable error. +func NewUpdateCampaignServiceUnavailable(body *UpdateCampaignServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignInternalServerError builds a lfx-v2-campaign-service-briefs +// service update-campaign endpoint InternalServerError error. +func NewUpdateCampaignInternalServerError(body *UpdateCampaignInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignNotFound builds a lfx-v2-campaign-service-briefs service +// update-campaign endpoint NotFound error. +func NewUpdateCampaignNotFound(body *UpdateCampaignNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignPreconditionFailed builds a lfx-v2-campaign-service-briefs +// service update-campaign endpoint PreconditionFailed error. +func NewUpdateCampaignPreconditionFailed(body *UpdateCampaignPreconditionFailedResponseBody) *lfxv2campaignservicebriefs.PreconditionFailedError { + v := &lfxv2campaignservicebriefs.PreconditionFailedError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewUpdateCampaignPreconditionRequired builds a +// lfx-v2-campaign-service-briefs service update-campaign endpoint +// PreconditionRequired error. +func NewUpdateCampaignPreconditionRequired(body *UpdateCampaignPreconditionRequiredResponseBody) *lfxv2campaignservicebriefs.PreconditionRequiredError { + v := &lfxv2campaignservicebriefs.PreconditionRequiredError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetJobJobPollResponseOK builds a "lfx-v2-campaign-service-briefs" service +// "get-job" endpoint result from a HTTP "OK" response. +func NewGetJobJobPollResponseOK(body *GetJobResponseBody) *lfxv2campaignservicebriefs.JobPollResponse { + v := &lfxv2campaignservicebriefs.JobPollResponse{ + JobID: *body.JobID, + Status: *body.Status, + Error: body.Error, + } + if body.Result != nil { + v.Result = make([]*lfxv2campaignservicebriefs.PlatformResult, len(body.Result)) + for i, val := range body.Result { + if val == nil { + v.Result[i] = nil + continue + } + v.Result[i] = unmarshalPlatformResultResponseBodyToLfxv2campaignservicebriefsPlatformResult(val) + } + } + + return v +} + +// NewGetJobBadRequest builds a lfx-v2-campaign-service-briefs service get-job +// endpoint BadRequest error. +func NewGetJobBadRequest(body *GetJobBadRequestResponseBody) *lfxv2campaignservicebriefs.BadRequestError { + v := &lfxv2campaignservicebriefs.BadRequestError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetJobConflict builds a lfx-v2-campaign-service-briefs service get-job +// endpoint Conflict error. +func NewGetJobConflict(body *GetJobConflictResponseBody) *lfxv2campaignservicebriefs.ConflictError { + v := &lfxv2campaignservicebriefs.ConflictError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetJobServiceUnavailable builds a lfx-v2-campaign-service-briefs service +// get-job endpoint ServiceUnavailable error. +func NewGetJobServiceUnavailable(body *GetJobServiceUnavailableResponseBody) *lfxv2campaignservicebriefs.ConnServiceUnavailableError { + v := &lfxv2campaignservicebriefs.ConnServiceUnavailableError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetJobInternalServerError builds a lfx-v2-campaign-service-briefs service +// get-job endpoint InternalServerError error. +func NewGetJobInternalServerError(body *GetJobInternalServerErrorResponseBody) *lfxv2campaignservicebriefs.InternalServerError { + v := &lfxv2campaignservicebriefs.InternalServerError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// NewGetJobNotFound builds a lfx-v2-campaign-service-briefs service get-job +// endpoint NotFound error. +func NewGetJobNotFound(body *GetJobNotFoundResponseBody) *lfxv2campaignservicebriefs.NotFoundError { + v := &lfxv2campaignservicebriefs.NotFoundError{ + Code: *body.Code, + Message: *body.Message, + } + + return v +} + +// ValidateCreateBriefResponseBody runs the validations defined on +// Create-BriefResponseBody +func ValidateCreateBriefResponseBody(body *CreateBriefResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.ProgramType == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("program_type", "body")) + } + if body.EventSlug == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event_slug", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + if body.ProgramType != nil { + if !(*body.ProgramType == "events" || *body.ProgramType == "education" || *body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", *body.ProgramType, []any{"events", "education", "membership"})) + } + } + if body.Status != nil { + if !(*body.Status == "draft" || *body.Status == "approved" || *body.Status == "archived") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"draft", "approved", "archived"})) + } + } + return +} + +// ValidateGetBriefResponseBody runs the validations defined on +// Get-BriefResponseBody +func ValidateGetBriefResponseBody(body *GetBriefResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.ProgramType == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("program_type", "body")) + } + if body.EventSlug == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event_slug", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + if body.ProgramType != nil { + if !(*body.ProgramType == "events" || *body.ProgramType == "education" || *body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", *body.ProgramType, []any{"events", "education", "membership"})) + } + } + if body.Status != nil { + if !(*body.Status == "draft" || *body.Status == "approved" || *body.Status == "archived") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"draft", "approved", "archived"})) + } + } + return +} + +// ValidateUpdateBriefResponseBody runs the validations defined on +// Update-BriefResponseBody +func ValidateUpdateBriefResponseBody(body *UpdateBriefResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.ProgramType == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("program_type", "body")) + } + if body.EventSlug == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event_slug", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + if body.ProgramType != nil { + if !(*body.ProgramType == "events" || *body.ProgramType == "education" || *body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", *body.ProgramType, []any{"events", "education", "membership"})) + } + } + if body.Status != nil { + if !(*body.Status == "draft" || *body.Status == "approved" || *body.Status == "archived") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"draft", "approved", "archived"})) + } + } + return +} + +// ValidateApproveBriefResponseBody runs the validations defined on +// Approve-BriefResponseBody +func ValidateApproveBriefResponseBody(body *ApproveBriefResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.ProgramType == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("program_type", "body")) + } + if body.EventSlug == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event_slug", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + if body.ProgramType != nil { + if !(*body.ProgramType == "events" || *body.ProgramType == "education" || *body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", *body.ProgramType, []any{"events", "education", "membership"})) + } + } + if body.Status != nil { + if !(*body.Status == "draft" || *body.Status == "approved" || *body.Status == "archived") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"draft", "approved", "archived"})) + } + } + return +} + +// ValidateCreateCampaignsResponseBody runs the validations defined on +// Create-CampaignsResponseBody +func ValidateCreateCampaignsResponseBody(body *CreateCampaignsResponseBody) (err error) { + if body.JobID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("job_id", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Platforms == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platforms", "body")) + } + if body.Status != nil { + if !(*body.Status == "queued") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"queued"})) + } + } + return +} + +// ValidateGetCampaignResponseBody runs the validations defined on +// Get-CampaignResponseBody +func ValidateGetCampaignResponseBody(body *GetCampaignResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.BriefID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief_id", "body")) + } + if body.Platform == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platform", "body")) + } + if body.CampaignName == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("campaign_name", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + return +} + +// ValidateUpdateCampaignResponseBody runs the validations defined on +// Update-CampaignResponseBody +func ValidateUpdateCampaignResponseBody(body *UpdateCampaignResponseBody) (err error) { + if body.ID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("id", "body")) + } + if body.ProjectID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("project_id", "body")) + } + if body.BriefID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief_id", "body")) + } + if body.Platform == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platform", "body")) + } + if body.CampaignName == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("campaign_name", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Version == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("version", "body")) + } + return +} + +// ValidateGetJobResponseBody runs the validations defined on +// Get-JobResponseBody +func ValidateGetJobResponseBody(body *GetJobResponseBody) (err error) { + if body.JobID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("job_id", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Status != nil { + if !(*body.Status == "queued" || *body.Status == "running" || *body.Status == "succeeded" || *body.Status == "partial" || *body.Status == "failed") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.status", *body.Status, []any{"queued", "running", "succeeded", "partial", "failed"})) + } + } + for _, e := range body.Result { + if e != nil { + if err2 := ValidatePlatformResultResponseBody(e); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + return +} + +// ValidateCreateBriefBadRequestResponseBody runs the validations defined on +// create-brief_BadRequest_response_body +func ValidateCreateBriefBadRequestResponseBody(body *CreateBriefBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateBriefConflictResponseBody runs the validations defined on +// create-brief_Conflict_response_body +func ValidateCreateBriefConflictResponseBody(body *CreateBriefConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateBriefServiceUnavailableResponseBody runs the validations +// defined on create-brief_ServiceUnavailable_response_body +func ValidateCreateBriefServiceUnavailableResponseBody(body *CreateBriefServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateBriefInternalServerErrorResponseBody runs the validations +// defined on create-brief_InternalServerError_response_body +func ValidateCreateBriefInternalServerErrorResponseBody(body *CreateBriefInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateBriefNotFoundResponseBody runs the validations defined on +// create-brief_NotFound_response_body +func ValidateCreateBriefNotFoundResponseBody(body *CreateBriefNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetBriefBadRequestResponseBody runs the validations defined on +// get-brief_BadRequest_response_body +func ValidateGetBriefBadRequestResponseBody(body *GetBriefBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetBriefConflictResponseBody runs the validations defined on +// get-brief_Conflict_response_body +func ValidateGetBriefConflictResponseBody(body *GetBriefConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetBriefServiceUnavailableResponseBody runs the validations defined +// on get-brief_ServiceUnavailable_response_body +func ValidateGetBriefServiceUnavailableResponseBody(body *GetBriefServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetBriefInternalServerErrorResponseBody runs the validations defined +// on get-brief_InternalServerError_response_body +func ValidateGetBriefInternalServerErrorResponseBody(body *GetBriefInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetBriefNotFoundResponseBody runs the validations defined on +// get-brief_NotFound_response_body +func ValidateGetBriefNotFoundResponseBody(body *GetBriefNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefBadRequestResponseBody runs the validations defined on +// update-brief_BadRequest_response_body +func ValidateUpdateBriefBadRequestResponseBody(body *UpdateBriefBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefConflictResponseBody runs the validations defined on +// update-brief_Conflict_response_body +func ValidateUpdateBriefConflictResponseBody(body *UpdateBriefConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefServiceUnavailableResponseBody runs the validations +// defined on update-brief_ServiceUnavailable_response_body +func ValidateUpdateBriefServiceUnavailableResponseBody(body *UpdateBriefServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefInternalServerErrorResponseBody runs the validations +// defined on update-brief_InternalServerError_response_body +func ValidateUpdateBriefInternalServerErrorResponseBody(body *UpdateBriefInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefNotFoundResponseBody runs the validations defined on +// update-brief_NotFound_response_body +func ValidateUpdateBriefNotFoundResponseBody(body *UpdateBriefNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefPreconditionFailedResponseBody runs the validations +// defined on update-brief_PreconditionFailed_response_body +func ValidateUpdateBriefPreconditionFailedResponseBody(body *UpdateBriefPreconditionFailedResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateBriefPreconditionRequiredResponseBody runs the validations +// defined on update-brief_PreconditionRequired_response_body +func ValidateUpdateBriefPreconditionRequiredResponseBody(body *UpdateBriefPreconditionRequiredResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefBadRequestResponseBody runs the validations defined on +// approve-brief_BadRequest_response_body +func ValidateApproveBriefBadRequestResponseBody(body *ApproveBriefBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefConflictResponseBody runs the validations defined on +// approve-brief_Conflict_response_body +func ValidateApproveBriefConflictResponseBody(body *ApproveBriefConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefServiceUnavailableResponseBody runs the validations +// defined on approve-brief_ServiceUnavailable_response_body +func ValidateApproveBriefServiceUnavailableResponseBody(body *ApproveBriefServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefInternalServerErrorResponseBody runs the validations +// defined on approve-brief_InternalServerError_response_body +func ValidateApproveBriefInternalServerErrorResponseBody(body *ApproveBriefInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefNotFoundResponseBody runs the validations defined on +// approve-brief_NotFound_response_body +func ValidateApproveBriefNotFoundResponseBody(body *ApproveBriefNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefPreconditionFailedResponseBody runs the validations +// defined on approve-brief_PreconditionFailed_response_body +func ValidateApproveBriefPreconditionFailedResponseBody(body *ApproveBriefPreconditionFailedResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateApproveBriefPreconditionRequiredResponseBody runs the validations +// defined on approve-brief_PreconditionRequired_response_body +func ValidateApproveBriefPreconditionRequiredResponseBody(body *ApproveBriefPreconditionRequiredResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateDeleteBriefBadRequestResponseBody runs the validations defined on +// delete-brief_BadRequest_response_body +func ValidateDeleteBriefBadRequestResponseBody(body *DeleteBriefBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateDeleteBriefConflictResponseBody runs the validations defined on +// delete-brief_Conflict_response_body +func ValidateDeleteBriefConflictResponseBody(body *DeleteBriefConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateDeleteBriefServiceUnavailableResponseBody runs the validations +// defined on delete-brief_ServiceUnavailable_response_body +func ValidateDeleteBriefServiceUnavailableResponseBody(body *DeleteBriefServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateDeleteBriefInternalServerErrorResponseBody runs the validations +// defined on delete-brief_InternalServerError_response_body +func ValidateDeleteBriefInternalServerErrorResponseBody(body *DeleteBriefInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateDeleteBriefNotFoundResponseBody runs the validations defined on +// delete-brief_NotFound_response_body +func ValidateDeleteBriefNotFoundResponseBody(body *DeleteBriefNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateCampaignsBadRequestResponseBody runs the validations defined +// on create-campaigns_BadRequest_response_body +func ValidateCreateCampaignsBadRequestResponseBody(body *CreateCampaignsBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateCampaignsConflictResponseBody runs the validations defined on +// create-campaigns_Conflict_response_body +func ValidateCreateCampaignsConflictResponseBody(body *CreateCampaignsConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateCampaignsServiceUnavailableResponseBody runs the validations +// defined on create-campaigns_ServiceUnavailable_response_body +func ValidateCreateCampaignsServiceUnavailableResponseBody(body *CreateCampaignsServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateCampaignsInternalServerErrorResponseBody runs the validations +// defined on create-campaigns_InternalServerError_response_body +func ValidateCreateCampaignsInternalServerErrorResponseBody(body *CreateCampaignsInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateCreateCampaignsNotFoundResponseBody runs the validations defined on +// create-campaigns_NotFound_response_body +func ValidateCreateCampaignsNotFoundResponseBody(body *CreateCampaignsNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetCampaignBadRequestResponseBody runs the validations defined on +// get-campaign_BadRequest_response_body +func ValidateGetCampaignBadRequestResponseBody(body *GetCampaignBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetCampaignConflictResponseBody runs the validations defined on +// get-campaign_Conflict_response_body +func ValidateGetCampaignConflictResponseBody(body *GetCampaignConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetCampaignServiceUnavailableResponseBody runs the validations +// defined on get-campaign_ServiceUnavailable_response_body +func ValidateGetCampaignServiceUnavailableResponseBody(body *GetCampaignServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetCampaignInternalServerErrorResponseBody runs the validations +// defined on get-campaign_InternalServerError_response_body +func ValidateGetCampaignInternalServerErrorResponseBody(body *GetCampaignInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetCampaignNotFoundResponseBody runs the validations defined on +// get-campaign_NotFound_response_body +func ValidateGetCampaignNotFoundResponseBody(body *GetCampaignNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignBadRequestResponseBody runs the validations defined on +// update-campaign_BadRequest_response_body +func ValidateUpdateCampaignBadRequestResponseBody(body *UpdateCampaignBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignConflictResponseBody runs the validations defined on +// update-campaign_Conflict_response_body +func ValidateUpdateCampaignConflictResponseBody(body *UpdateCampaignConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignServiceUnavailableResponseBody runs the validations +// defined on update-campaign_ServiceUnavailable_response_body +func ValidateUpdateCampaignServiceUnavailableResponseBody(body *UpdateCampaignServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignInternalServerErrorResponseBody runs the validations +// defined on update-campaign_InternalServerError_response_body +func ValidateUpdateCampaignInternalServerErrorResponseBody(body *UpdateCampaignInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignNotFoundResponseBody runs the validations defined on +// update-campaign_NotFound_response_body +func ValidateUpdateCampaignNotFoundResponseBody(body *UpdateCampaignNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignPreconditionFailedResponseBody runs the validations +// defined on update-campaign_PreconditionFailed_response_body +func ValidateUpdateCampaignPreconditionFailedResponseBody(body *UpdateCampaignPreconditionFailedResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateUpdateCampaignPreconditionRequiredResponseBody runs the validations +// defined on update-campaign_PreconditionRequired_response_body +func ValidateUpdateCampaignPreconditionRequiredResponseBody(body *UpdateCampaignPreconditionRequiredResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetJobBadRequestResponseBody runs the validations defined on +// get-job_BadRequest_response_body +func ValidateGetJobBadRequestResponseBody(body *GetJobBadRequestResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetJobConflictResponseBody runs the validations defined on +// get-job_Conflict_response_body +func ValidateGetJobConflictResponseBody(body *GetJobConflictResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetJobServiceUnavailableResponseBody runs the validations defined on +// get-job_ServiceUnavailable_response_body +func ValidateGetJobServiceUnavailableResponseBody(body *GetJobServiceUnavailableResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetJobInternalServerErrorResponseBody runs the validations defined +// on get-job_InternalServerError_response_body +func ValidateGetJobInternalServerErrorResponseBody(body *GetJobInternalServerErrorResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateGetJobNotFoundResponseBody runs the validations defined on +// get-job_NotFound_response_body +func ValidateGetJobNotFoundResponseBody(body *GetJobNotFoundResponseBody) (err error) { + if body.Code == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("code", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + +// ValidateBriefInputRequestBody runs the validations defined on +// brief-inputRequestBody +func ValidateBriefInputRequestBody(body *BriefInputRequestBody) (err error) { + if !(body.ProgramType == "events" || body.ProgramType == "education" || body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", body.ProgramType, []any{"events", "education", "membership"})) + } + return +} + +// ValidateCampaignCreateInputRequestBody runs the validations defined on +// campaign-create-inputRequestBody +func ValidateCampaignCreateInputRequestBody(body *CampaignCreateInputRequestBody) (err error) { + if body.Platforms == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platforms", "body")) + } + if len(body.Platforms) < 1 { + err = goa.MergeErrors(err, goa.InvalidLengthError("body.platforms", body.Platforms, len(body.Platforms), 1, true)) + } + for _, e := range body.Platforms { + if !(e == "google-ads" || e == "linkedin-ads" || e == "meta-ads" || e == "reddit-ads" || e == "twitter-ads" || e == "microsoft-ads" || e == "hubspot") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.platforms[*]", e, []any{"google-ads", "linkedin-ads", "meta-ads", "reddit-ads", "twitter-ads", "microsoft-ads", "hubspot"})) + } + } + return +} + +// ValidatePlatformResultResponseBody runs the validations defined on +// platform-resultResponseBody +func ValidatePlatformResultResponseBody(body *PlatformResultResponseBody) (err error) { + if body.Platform == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platform", "body")) + } + if body.OK == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("ok", "body")) + } + return +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/server/encode_decode.go b/gen/http/lfx_v2_campaign_service_briefs/server/encode_decode.go new file mode 100644 index 00000000..d26afdc1 --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/server/encode_decode.go @@ -0,0 +1,1433 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP server encoders and decoders +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package server + +import ( + "context" + "errors" + "io" + "net/http" + "strings" + + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// EncodeCreateBriefResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs create-brief endpoint. +func EncodeCreateBriefResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Brief) + enc := encoder(ctx, w) + body := NewCreateBriefResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusCreated) + return enc.Encode(body) + } +} + +// DecodeCreateBriefRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs create-brief endpoint. +func DecodeCreateBriefRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.CreateBriefPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.CreateBriefPayload, error) { + var payload *lfxv2campaignservicebriefs.CreateBriefPayload + var ( + body CreateBriefRequestBody + err error + ) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return payload, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return payload, gerr + } + return payload, goa.DecodePayloadError(err.Error()) + } + err = ValidateCreateBriefRequestBody(&body) + if err != nil { + return payload, err + } + + var ( + projectID string + bearerToken *string + + params = mux.Vars(r) + ) + projectID = params["project_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload = NewCreateBriefPayload(&body, projectID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeCreateBriefError returns an encoder for errors returned by the +// create-brief lfx-v2-campaign-service-briefs endpoint. +func EncodeCreateBriefError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateBriefBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateBriefConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateBriefServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateBriefInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateBriefNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeGetBriefResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs get-brief endpoint. +func EncodeGetBriefResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Brief) + enc := encoder(ctx, w) + body := NewGetBriefResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeGetBriefRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs get-brief endpoint. +func DecodeGetBriefRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.GetBriefPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.GetBriefPayload, error) { + var payload *lfxv2campaignservicebriefs.GetBriefPayload + var ( + projectID string + briefID string + bearerToken *string + err error + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return payload, err + } + payload = NewGetBriefPayload(projectID, briefID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeGetBriefError returns an encoder for errors returned by the get-brief +// lfx-v2-campaign-service-briefs endpoint. +func EncodeGetBriefError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetBriefBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetBriefConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetBriefServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetBriefInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetBriefNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeUpdateBriefResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs update-brief endpoint. +func EncodeUpdateBriefResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Brief) + enc := encoder(ctx, w) + body := NewUpdateBriefResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeUpdateBriefRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs update-brief endpoint. +func DecodeUpdateBriefRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.UpdateBriefPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.UpdateBriefPayload, error) { + var payload *lfxv2campaignservicebriefs.UpdateBriefPayload + var ( + body UpdateBriefRequestBody + err error + ) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return payload, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return payload, gerr + } + return payload, goa.DecodePayloadError(err.Error()) + } + err = ValidateUpdateBriefRequestBody(&body) + if err != nil { + return payload, err + } + + var ( + projectID string + briefID string + bearerToken *string + ifMatch *string + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + ifMatchRaw := r.Header.Get("If-Match") + if ifMatchRaw != "" { + ifMatch = &ifMatchRaw + } + if err != nil { + return payload, err + } + payload = NewUpdateBriefPayload(&body, projectID, briefID, bearerToken, ifMatch) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeUpdateBriefError returns an encoder for errors returned by the +// update-brief lfx-v2-campaign-service-briefs endpoint. +func EncodeUpdateBriefError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + case "PreconditionFailed": + var res *lfxv2campaignservicebriefs.PreconditionFailedError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefPreconditionFailedResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionFailed) + return enc.Encode(body) + case "PreconditionRequired": + var res *lfxv2campaignservicebriefs.PreconditionRequiredError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateBriefPreconditionRequiredResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionRequired) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeApproveBriefResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs approve-brief endpoint. +func EncodeApproveBriefResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Brief) + enc := encoder(ctx, w) + body := NewApproveBriefResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeApproveBriefRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs approve-brief endpoint. +func DecodeApproveBriefRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.ApproveBriefPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.ApproveBriefPayload, error) { + var payload *lfxv2campaignservicebriefs.ApproveBriefPayload + var ( + projectID string + briefID string + bearerToken *string + ifMatch *string + err error + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + ifMatchRaw := r.Header.Get("If-Match") + if ifMatchRaw != "" { + ifMatch = &ifMatchRaw + } + if err != nil { + return payload, err + } + payload = NewApproveBriefPayload(projectID, briefID, bearerToken, ifMatch) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeApproveBriefError returns an encoder for errors returned by the +// approve-brief lfx-v2-campaign-service-briefs endpoint. +func EncodeApproveBriefError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + case "PreconditionFailed": + var res *lfxv2campaignservicebriefs.PreconditionFailedError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefPreconditionFailedResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionFailed) + return enc.Encode(body) + case "PreconditionRequired": + var res *lfxv2campaignservicebriefs.PreconditionRequiredError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewApproveBriefPreconditionRequiredResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionRequired) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeDeleteBriefResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs delete-brief endpoint. +func EncodeDeleteBriefResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + w.WriteHeader(http.StatusNoContent) + return nil + } +} + +// DecodeDeleteBriefRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs delete-brief endpoint. +func DecodeDeleteBriefRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.DeleteBriefPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.DeleteBriefPayload, error) { + var payload *lfxv2campaignservicebriefs.DeleteBriefPayload + var ( + projectID string + briefID string + bearerToken *string + err error + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return payload, err + } + payload = NewDeleteBriefPayload(projectID, briefID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeDeleteBriefError returns an encoder for errors returned by the +// delete-brief lfx-v2-campaign-service-briefs endpoint. +func EncodeDeleteBriefError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewDeleteBriefBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewDeleteBriefConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewDeleteBriefServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewDeleteBriefInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewDeleteBriefNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeCreateCampaignsResponse returns an encoder for responses returned by +// the lfx-v2-campaign-service-briefs create-campaigns endpoint. +func EncodeCreateCampaignsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.JobCreateResponse) + enc := encoder(ctx, w) + body := NewCreateCampaignsResponseBody(res) + w.WriteHeader(http.StatusAccepted) + return enc.Encode(body) + } +} + +// DecodeCreateCampaignsRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs create-campaigns endpoint. +func DecodeCreateCampaignsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.CreateCampaignsPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.CreateCampaignsPayload, error) { + var payload *lfxv2campaignservicebriefs.CreateCampaignsPayload + var ( + body CreateCampaignsRequestBody + err error + ) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return payload, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return payload, gerr + } + return payload, goa.DecodePayloadError(err.Error()) + } + err = ValidateCreateCampaignsRequestBody(&body) + if err != nil { + return payload, err + } + + var ( + projectID string + briefID string + bearerToken *string + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return payload, err + } + payload = NewCreateCampaignsPayload(&body, projectID, briefID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeCreateCampaignsError returns an encoder for errors returned by the +// create-campaigns lfx-v2-campaign-service-briefs endpoint. +func EncodeCreateCampaignsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateCampaignsBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateCampaignsConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateCampaignsServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateCampaignsInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCreateCampaignsNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeGetCampaignResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs get-campaign endpoint. +func EncodeGetCampaignResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Campaign) + enc := encoder(ctx, w) + body := NewGetCampaignResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeGetCampaignRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs get-campaign endpoint. +func DecodeGetCampaignRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.GetCampaignPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.GetCampaignPayload, error) { + var payload *lfxv2campaignservicebriefs.GetCampaignPayload + var ( + projectID string + briefID string + campaignID string + bearerToken *string + err error + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + campaignID = params["campaign_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("campaign_id", campaignID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return payload, err + } + payload = NewGetCampaignPayload(projectID, briefID, campaignID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeGetCampaignError returns an encoder for errors returned by the +// get-campaign lfx-v2-campaign-service-briefs endpoint. +func EncodeGetCampaignError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetCampaignBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetCampaignConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetCampaignServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetCampaignInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetCampaignNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeUpdateCampaignResponse returns an encoder for responses returned by +// the lfx-v2-campaign-service-briefs update-campaign endpoint. +func EncodeUpdateCampaignResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.Campaign) + enc := encoder(ctx, w) + body := NewUpdateCampaignResponseBody(res) + if res.Etag != nil { + w.Header().Set("Etag", *res.Etag) + } + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeUpdateCampaignRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs update-campaign endpoint. +func DecodeUpdateCampaignRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.UpdateCampaignPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.UpdateCampaignPayload, error) { + var payload *lfxv2campaignservicebriefs.UpdateCampaignPayload + var ( + body UpdateCampaignRequestBody + err error + ) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return payload, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return payload, gerr + } + return payload, goa.DecodePayloadError(err.Error()) + } + err = ValidateUpdateCampaignRequestBody(&body) + if err != nil { + return payload, err + } + + var ( + projectID string + briefID string + campaignID string + bearerToken *string + ifMatch *string + + params = mux.Vars(r) + ) + projectID = params["project_id"] + briefID = params["brief_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("brief_id", briefID, goa.FormatUUID)) + campaignID = params["campaign_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("campaign_id", campaignID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + ifMatchRaw := r.Header.Get("If-Match") + if ifMatchRaw != "" { + ifMatch = &ifMatchRaw + } + if err != nil { + return payload, err + } + payload = NewUpdateCampaignPayload(&body, projectID, briefID, campaignID, bearerToken, ifMatch) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeUpdateCampaignError returns an encoder for errors returned by the +// update-campaign lfx-v2-campaign-service-briefs endpoint. +func EncodeUpdateCampaignError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + case "PreconditionFailed": + var res *lfxv2campaignservicebriefs.PreconditionFailedError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignPreconditionFailedResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionFailed) + return enc.Encode(body) + case "PreconditionRequired": + var res *lfxv2campaignservicebriefs.PreconditionRequiredError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateCampaignPreconditionRequiredResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusPreconditionRequired) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeGetJobResponse returns an encoder for responses returned by the +// lfx-v2-campaign-service-briefs get-job endpoint. +func EncodeGetJobResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*lfxv2campaignservicebriefs.JobPollResponse) + enc := encoder(ctx, w) + body := NewGetJobResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeGetJobRequest returns a decoder for requests sent to the +// lfx-v2-campaign-service-briefs get-job endpoint. +func DecodeGetJobRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*lfxv2campaignservicebriefs.GetJobPayload, error) { + return func(r *http.Request) (*lfxv2campaignservicebriefs.GetJobPayload, error) { + var payload *lfxv2campaignservicebriefs.GetJobPayload + var ( + projectID string + jobID string + bearerToken *string + err error + + params = mux.Vars(r) + ) + projectID = params["project_id"] + jobID = params["job_id"] + err = goa.MergeErrors(err, goa.ValidateFormat("job_id", jobID, goa.FormatUUID)) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return payload, err + } + payload = NewGetJobPayload(projectID, jobID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeGetJobError returns an encoder for errors returned by the get-job +// lfx-v2-campaign-service-briefs endpoint. +func EncodeGetJobError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *lfxv2campaignservicebriefs.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJobBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "Conflict": + var res *lfxv2campaignservicebriefs.ConflictError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJobConflictResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusConflict) + return enc.Encode(body) + case "ServiceUnavailable": + var res *lfxv2campaignservicebriefs.ConnServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJobServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + case "InternalServerError": + var res *lfxv2campaignservicebriefs.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJobInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "NotFound": + var res *lfxv2campaignservicebriefs.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJobNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// unmarshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput builds +// a value of type *lfxv2campaignservicebriefs.BriefInput from a value of type +// *BriefInputRequestBody. +func unmarshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(v *BriefInputRequestBody) *lfxv2campaignservicebriefs.BriefInput { + res := &lfxv2campaignservicebriefs.BriefInput{ + ProgramType: *v.ProgramType, + EventSlug: *v.EventSlug, + URL: v.URL, + EventDetails: v.EventDetails, + Copy: v.Copy, + Keywords: v.Keywords, + Targeting: v.Targeting, + } + if v.Platforms != nil { + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + } + + return res +} + +// unmarshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput +// builds a value of type *lfxv2campaignservicebriefs.CampaignCreateInput from +// a value of type *CampaignCreateInputRequestBody. +func unmarshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput(v *CampaignCreateInputRequestBody) *lfxv2campaignservicebriefs.CampaignCreateInput { + res := &lfxv2campaignservicebriefs.CampaignCreateInput{ + Config: v.Config, + } + res.Platforms = make([]string, len(v.Platforms)) + for i, val := range v.Platforms { + res.Platforms[i] = val + } + + return res +} + +// unmarshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput +// builds a value of type *lfxv2campaignservicebriefs.CampaignUpdateInput from +// a value of type *CampaignUpdateInputRequestBody. +func unmarshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput(v *CampaignUpdateInputRequestBody) *lfxv2campaignservicebriefs.CampaignUpdateInput { + res := &lfxv2campaignservicebriefs.CampaignUpdateInput{ + CampaignName: *v.CampaignName, + Status: *v.Status, + Config: v.Config, + } + + return res +} + +// marshalLfxv2campaignservicebriefsPlatformResultToPlatformResultResponseBody +// builds a value of type *PlatformResultResponseBody from a value of type +// *lfxv2campaignservicebriefs.PlatformResult. +func marshalLfxv2campaignservicebriefsPlatformResultToPlatformResultResponseBody(v *lfxv2campaignservicebriefs.PlatformResult) *PlatformResultResponseBody { + if v == nil { + return nil + } + res := &PlatformResultResponseBody{ + Platform: v.Platform, + OK: v.OK, + CampaignID: v.CampaignID, + Error: v.Error, + } + + return res +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/server/paths.go b/gen/http/lfx_v2_campaign_service_briefs/server/paths.go new file mode 100644 index 00000000..352e66ae --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/server/paths.go @@ -0,0 +1,57 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// HTTP request path constructors for the lfx-v2-campaign-service-briefs service. +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package server + +import ( + "fmt" +) + +// CreateBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service create-brief HTTP endpoint. +func CreateBriefLfxV2CampaignServiceBriefsPath(projectID string) string { + return fmt.Sprintf("/projects/%v/briefs", projectID) +} + +// GetBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-brief HTTP endpoint. +func GetBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// UpdateBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service update-brief HTTP endpoint. +func UpdateBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// ApproveBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service approve-brief HTTP endpoint. +func ApproveBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/approve", projectID, briefID) +} + +// DeleteBriefLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service delete-brief HTTP endpoint. +func DeleteBriefLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v", projectID, briefID) +} + +// CreateCampaignsLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service create-campaigns HTTP endpoint. +func CreateCampaignsLfxV2CampaignServiceBriefsPath(projectID string, briefID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns", projectID, briefID) +} + +// GetCampaignLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-campaign HTTP endpoint. +func GetCampaignLfxV2CampaignServiceBriefsPath(projectID string, briefID string, campaignID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns/%v", projectID, briefID, campaignID) +} + +// UpdateCampaignLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service update-campaign HTTP endpoint. +func UpdateCampaignLfxV2CampaignServiceBriefsPath(projectID string, briefID string, campaignID string) string { + return fmt.Sprintf("/projects/%v/briefs/%v/campaigns/%v", projectID, briefID, campaignID) +} + +// GetJobLfxV2CampaignServiceBriefsPath returns the URL path to the lfx-v2-campaign-service-briefs service get-job HTTP endpoint. +func GetJobLfxV2CampaignServiceBriefsPath(projectID string, jobID string) string { + return fmt.Sprintf("/projects/%v/jobs/%v", projectID, jobID) +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/server/server.go b/gen/http/lfx_v2_campaign_service_briefs/server/server.go new file mode 100644 index 00000000..f839b3cb --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/server/server.go @@ -0,0 +1,604 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP server +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package server + +import ( + "context" + "net/http" + + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// Server lists the lfx-v2-campaign-service-briefs service endpoint HTTP +// handlers. +type Server struct { + Mounts []*MountPoint + CreateBrief http.Handler + GetBrief http.Handler + UpdateBrief http.Handler + ApproveBrief http.Handler + DeleteBrief http.Handler + CreateCampaigns http.Handler + GetCampaign http.Handler + UpdateCampaign http.Handler + GetJob http.Handler +} + +// MountPoint holds information about the mounted endpoints. +type MountPoint struct { + // Method is the name of the service method served by the mounted HTTP handler. + Method string + // Verb is the HTTP method used to match requests to the mounted handler. + Verb string + // Pattern is the HTTP request path pattern used to match requests to the + // mounted handler. + Pattern string +} + +// New instantiates HTTP handlers for all the lfx-v2-campaign-service-briefs +// service endpoints using the provided encoder and decoder. The handlers are +// mounted on the given mux using the HTTP verb and path defined in the design. +// errhandler is called whenever a response fails to be encoded. formatter is +// used to format errors returned by the service methods prior to encoding. +// Both errhandler and formatter are optional and can be nil. +func New( + e *lfxv2campaignservicebriefs.Endpoints, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) *Server { + return &Server{ + Mounts: []*MountPoint{ + {"CreateBrief", "POST", "/projects/{project_id}/briefs"}, + {"GetBrief", "GET", "/projects/{project_id}/briefs/{brief_id}"}, + {"UpdateBrief", "PUT", "/projects/{project_id}/briefs/{brief_id}"}, + {"ApproveBrief", "POST", "/projects/{project_id}/briefs/{brief_id}/approve"}, + {"DeleteBrief", "DELETE", "/projects/{project_id}/briefs/{brief_id}"}, + {"CreateCampaigns", "POST", "/projects/{project_id}/briefs/{brief_id}/campaigns"}, + {"GetCampaign", "GET", "/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}"}, + {"UpdateCampaign", "PUT", "/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}"}, + {"GetJob", "GET", "/projects/{project_id}/jobs/{job_id}"}, + }, + CreateBrief: NewCreateBriefHandler(e.CreateBrief, mux, decoder, encoder, errhandler, formatter), + GetBrief: NewGetBriefHandler(e.GetBrief, mux, decoder, encoder, errhandler, formatter), + UpdateBrief: NewUpdateBriefHandler(e.UpdateBrief, mux, decoder, encoder, errhandler, formatter), + ApproveBrief: NewApproveBriefHandler(e.ApproveBrief, mux, decoder, encoder, errhandler, formatter), + DeleteBrief: NewDeleteBriefHandler(e.DeleteBrief, mux, decoder, encoder, errhandler, formatter), + CreateCampaigns: NewCreateCampaignsHandler(e.CreateCampaigns, mux, decoder, encoder, errhandler, formatter), + GetCampaign: NewGetCampaignHandler(e.GetCampaign, mux, decoder, encoder, errhandler, formatter), + UpdateCampaign: NewUpdateCampaignHandler(e.UpdateCampaign, mux, decoder, encoder, errhandler, formatter), + GetJob: NewGetJobHandler(e.GetJob, mux, decoder, encoder, errhandler, formatter), + } +} + +// Service returns the name of the service served. +func (s *Server) Service() string { return "lfx-v2-campaign-service-briefs" } + +// Use wraps the server handlers with the given middleware. +func (s *Server) Use(m func(http.Handler) http.Handler) { + s.CreateBrief = m(s.CreateBrief) + s.GetBrief = m(s.GetBrief) + s.UpdateBrief = m(s.UpdateBrief) + s.ApproveBrief = m(s.ApproveBrief) + s.DeleteBrief = m(s.DeleteBrief) + s.CreateCampaigns = m(s.CreateCampaigns) + s.GetCampaign = m(s.GetCampaign) + s.UpdateCampaign = m(s.UpdateCampaign) + s.GetJob = m(s.GetJob) +} + +// MethodNames returns the methods served. +func (s *Server) MethodNames() []string { return lfxv2campaignservicebriefs.MethodNames[:] } + +// Mount configures the mux to serve the lfx-v2-campaign-service-briefs +// endpoints. +func Mount(mux goahttp.Muxer, h *Server) { + MountCreateBriefHandler(mux, h.CreateBrief) + MountGetBriefHandler(mux, h.GetBrief) + MountUpdateBriefHandler(mux, h.UpdateBrief) + MountApproveBriefHandler(mux, h.ApproveBrief) + MountDeleteBriefHandler(mux, h.DeleteBrief) + MountCreateCampaignsHandler(mux, h.CreateCampaigns) + MountGetCampaignHandler(mux, h.GetCampaign) + MountUpdateCampaignHandler(mux, h.UpdateCampaign) + MountGetJobHandler(mux, h.GetJob) +} + +// Mount configures the mux to serve the lfx-v2-campaign-service-briefs +// endpoints. +func (s *Server) Mount(mux goahttp.Muxer) { + Mount(mux, s) +} + +// MountCreateBriefHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint. +func MountCreateBriefHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("POST", "/projects/{project_id}/briefs", f) +} + +// NewCreateBriefHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "create-brief" +// endpoint. +func NewCreateBriefHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeCreateBriefRequest(mux, decoder) + encodeResponse = EncodeCreateBriefResponse(encoder) + encodeError = EncodeCreateBriefError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "create-brief") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountGetBriefHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint. +func MountGetBriefHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/projects/{project_id}/briefs/{brief_id}", f) +} + +// NewGetBriefHandler creates a HTTP handler which loads the HTTP request and +// calls the "lfx-v2-campaign-service-briefs" service "get-brief" endpoint. +func NewGetBriefHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeGetBriefRequest(mux, decoder) + encodeResponse = EncodeGetBriefResponse(encoder) + encodeError = EncodeGetBriefError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "get-brief") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountUpdateBriefHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint. +func MountUpdateBriefHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("PUT", "/projects/{project_id}/briefs/{brief_id}", f) +} + +// NewUpdateBriefHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "update-brief" +// endpoint. +func NewUpdateBriefHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeUpdateBriefRequest(mux, decoder) + encodeResponse = EncodeUpdateBriefResponse(encoder) + encodeError = EncodeUpdateBriefError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "update-brief") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountApproveBriefHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint. +func MountApproveBriefHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("POST", "/projects/{project_id}/briefs/{brief_id}/approve", f) +} + +// NewApproveBriefHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "approve-brief" +// endpoint. +func NewApproveBriefHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeApproveBriefRequest(mux, decoder) + encodeResponse = EncodeApproveBriefResponse(encoder) + encodeError = EncodeApproveBriefError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "approve-brief") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountDeleteBriefHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint. +func MountDeleteBriefHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("DELETE", "/projects/{project_id}/briefs/{brief_id}", f) +} + +// NewDeleteBriefHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "delete-brief" +// endpoint. +func NewDeleteBriefHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeDeleteBriefRequest(mux, decoder) + encodeResponse = EncodeDeleteBriefResponse(encoder) + encodeError = EncodeDeleteBriefError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "delete-brief") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountCreateCampaignsHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint. +func MountCreateCampaignsHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("POST", "/projects/{project_id}/briefs/{brief_id}/campaigns", f) +} + +// NewCreateCampaignsHandler creates a HTTP handler which loads the HTTP +// request and calls the "lfx-v2-campaign-service-briefs" service +// "create-campaigns" endpoint. +func NewCreateCampaignsHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeCreateCampaignsRequest(mux, decoder) + encodeResponse = EncodeCreateCampaignsResponse(encoder) + encodeError = EncodeCreateCampaignsError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "create-campaigns") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountGetCampaignHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint. +func MountGetCampaignHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}", f) +} + +// NewGetCampaignHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "get-campaign" +// endpoint. +func NewGetCampaignHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeGetCampaignRequest(mux, decoder) + encodeResponse = EncodeGetCampaignResponse(encoder) + encodeError = EncodeGetCampaignError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "get-campaign") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountUpdateCampaignHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint. +func MountUpdateCampaignHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("PUT", "/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}", f) +} + +// NewUpdateCampaignHandler creates a HTTP handler which loads the HTTP request +// and calls the "lfx-v2-campaign-service-briefs" service "update-campaign" +// endpoint. +func NewUpdateCampaignHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeUpdateCampaignRequest(mux, decoder) + encodeResponse = EncodeUpdateCampaignResponse(encoder) + encodeError = EncodeUpdateCampaignError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "update-campaign") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountGetJobHandler configures the mux to serve the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint. +func MountGetJobHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/projects/{project_id}/jobs/{job_id}", f) +} + +// NewGetJobHandler creates a HTTP handler which loads the HTTP request and +// calls the "lfx-v2-campaign-service-briefs" service "get-job" endpoint. +func NewGetJobHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeGetJobRequest(mux, decoder) + encodeResponse = EncodeGetJobResponse(encoder) + encodeError = EncodeGetJobError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "get-job") + ctx = context.WithValue(ctx, goa.ServiceKey, "lfx-v2-campaign-service-briefs") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} diff --git a/gen/http/lfx_v2_campaign_service_briefs/server/types.go b/gen/http/lfx_v2_campaign_service_briefs/server/types.go new file mode 100644 index 00000000..dc45edb9 --- /dev/null +++ b/gen/http/lfx_v2_campaign_service_briefs/server/types.go @@ -0,0 +1,1733 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs HTTP server types +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package server + +import ( + lfxv2campaignservicebriefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + goa "goa.design/goa/v3/pkg" +) + +// CreateBriefRequestBody is the type of the "lfx-v2-campaign-service-briefs" +// service "create-brief" endpoint HTTP request body. +type CreateBriefRequestBody struct { + Brief *BriefInputRequestBody `form:"brief,omitempty" json:"brief,omitempty" xml:"brief,omitempty"` +} + +// UpdateBriefRequestBody is the type of the "lfx-v2-campaign-service-briefs" +// service "update-brief" endpoint HTTP request body. +type UpdateBriefRequestBody struct { + Brief *BriefInputRequestBody `form:"brief,omitempty" json:"brief,omitempty" xml:"brief,omitempty"` +} + +// CreateCampaignsRequestBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// request body. +type CreateCampaignsRequestBody struct { + Input *CampaignCreateInputRequestBody `form:"input,omitempty" json:"input,omitempty" xml:"input,omitempty"` +} + +// UpdateCampaignRequestBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// request body. +type UpdateCampaignRequestBody struct { + Campaign *CampaignUpdateInputRequestBody `form:"campaign,omitempty" json:"campaign,omitempty" xml:"campaign,omitempty"` +} + +// CreateBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "create-brief" endpoint HTTP response body. +type CreateBriefResponseBody struct { + // Brief UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Funnel context + ProgramType string `form:"program_type" json:"program_type" xml:"program_type"` + // Event/course slug (unique within the project) + EventSlug string `form:"event_slug" json:"event_slug" xml:"event_slug"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// GetBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-brief" endpoint HTTP response body. +type GetBriefResponseBody struct { + // Brief UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Funnel context + ProgramType string `form:"program_type" json:"program_type" xml:"program_type"` + // Event/course slug (unique within the project) + EventSlug string `form:"event_slug" json:"event_slug" xml:"event_slug"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// UpdateBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "update-brief" endpoint HTTP response body. +type UpdateBriefResponseBody struct { + // Brief UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Funnel context + ProgramType string `form:"program_type" json:"program_type" xml:"program_type"` + // Event/course slug (unique within the project) + EventSlug string `form:"event_slug" json:"event_slug" xml:"event_slug"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// ApproveBriefResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "approve-brief" endpoint HTTP response body. +type ApproveBriefResponseBody struct { + // Brief UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Funnel context + ProgramType string `form:"program_type" json:"program_type" xml:"program_type"` + // Event/course slug (unique within the project) + EventSlug string `form:"event_slug" json:"event_slug" xml:"event_slug"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` + // Lifecycle status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// CreateCampaignsResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body. +type CreateCampaignsResponseBody struct { + // Poll GET /projects/{projectId}/jobs/{jobId} + JobID string `form:"job_id" json:"job_id" xml:"job_id"` + // Initial status (always 'queued' on create) + Status string `form:"status" json:"status" xml:"status"` + // Platforms this job will create on + Platforms []string `form:"platforms" json:"platforms" xml:"platforms"` +} + +// GetCampaignResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-campaign" endpoint HTTP response body. +type GetCampaignResponseBody struct { + // Campaign UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Parent brief + BriefID string `form:"brief_id" json:"brief_id" xml:"brief_id"` + // Channel + Platform string `form:"platform" json:"platform" xml:"platform"` + // ID returned by the ad platform + PlatformCampaignID *string `form:"platform_campaign_id,omitempty" json:"platform_campaign_id,omitempty" xml:"platform_campaign_id,omitempty"` + // Campaign name + CampaignName string `form:"campaign_name" json:"campaign_name" xml:"campaign_name"` + // Campaign status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// UpdateCampaignResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body. +type UpdateCampaignResponseBody struct { + // Campaign UUID + ID string `form:"id" json:"id" xml:"id"` + // Owning project + ProjectID string `form:"project_id" json:"project_id" xml:"project_id"` + // Parent brief + BriefID string `form:"brief_id" json:"brief_id" xml:"brief_id"` + // Channel + Platform string `form:"platform" json:"platform" xml:"platform"` + // ID returned by the ad platform + PlatformCampaignID *string `form:"platform_campaign_id,omitempty" json:"platform_campaign_id,omitempty" xml:"platform_campaign_id,omitempty"` + // Campaign name + CampaignName string `form:"campaign_name" json:"campaign_name" xml:"campaign_name"` + // Campaign status + Status string `form:"status" json:"status" xml:"status"` + // Optimistic-concurrency version + Version int64 `form:"version" json:"version" xml:"version"` +} + +// GetJobResponseBody is the type of the "lfx-v2-campaign-service-briefs" +// service "get-job" endpoint HTTP response body. +type GetJobResponseBody struct { + // Job UUID + JobID string `form:"job_id" json:"job_id" xml:"job_id"` + // Job status + Status string `form:"status" json:"status" xml:"status"` + // Per-platform results, written once when the job reaches a terminal state + Result []*PlatformResultResponseBody `form:"result,omitempty" json:"result,omitempty" xml:"result,omitempty"` + // Terminal error, if any + Error *string `form:"error,omitempty" json:"error,omitempty" xml:"error,omitempty"` +} + +// CreateBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "BadRequest" error. +type CreateBriefBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "Conflict" error. +type CreateBriefConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type CreateBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type CreateBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-brief" endpoint HTTP +// response body for the "NotFound" error. +type CreateBriefNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "BadRequest" error. +type GetBriefBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "Conflict" error. +type GetBriefConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "InternalServerError" error. +type GetBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-brief" endpoint HTTP response +// body for the "NotFound" error. +type GetBriefNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "BadRequest" error. +type UpdateBriefBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "Conflict" error. +type UpdateBriefConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type UpdateBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type UpdateBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "NotFound" error. +type UpdateBriefNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "PreconditionFailed" error. +type UpdateBriefPreconditionFailedResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateBriefPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-brief" endpoint HTTP +// response body for the "PreconditionRequired" error. +type UpdateBriefPreconditionRequiredResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "BadRequest" error. +type ApproveBriefBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "Conflict" error. +type ApproveBriefConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type ApproveBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type ApproveBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "NotFound" error. +type ApproveBriefNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "PreconditionFailed" error. +type ApproveBriefPreconditionFailedResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// ApproveBriefPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "approve-brief" endpoint HTTP +// response body for the "PreconditionRequired" error. +type ApproveBriefPreconditionRequiredResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// DeleteBriefBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "BadRequest" error. +type DeleteBriefBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// DeleteBriefConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "Conflict" error. +type DeleteBriefConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// DeleteBriefServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type DeleteBriefServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// DeleteBriefInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "InternalServerError" error. +type DeleteBriefInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// DeleteBriefNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "delete-brief" endpoint HTTP +// response body for the "NotFound" error. +type DeleteBriefNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateCampaignsBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "BadRequest" error. +type CreateCampaignsBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateCampaignsConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "Conflict" error. +type CreateCampaignsConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateCampaignsServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type CreateCampaignsServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateCampaignsInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "InternalServerError" error. +type CreateCampaignsInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// CreateCampaignsNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "create-campaigns" endpoint HTTP +// response body for the "NotFound" error. +type CreateCampaignsNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetCampaignBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "BadRequest" error. +type GetCampaignBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetCampaignConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "Conflict" error. +type GetCampaignConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetCampaignServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type GetCampaignServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetCampaignInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "InternalServerError" error. +type GetCampaignInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetCampaignNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-campaign" endpoint HTTP +// response body for the "NotFound" error. +type GetCampaignNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "BadRequest" error. +type UpdateCampaignBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "Conflict" error. +type UpdateCampaignConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type UpdateCampaignServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "InternalServerError" error. +type UpdateCampaignInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "NotFound" error. +type UpdateCampaignNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignPreconditionFailedResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "PreconditionFailed" error. +type UpdateCampaignPreconditionFailedResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// UpdateCampaignPreconditionRequiredResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "update-campaign" endpoint HTTP +// response body for the "PreconditionRequired" error. +type UpdateCampaignPreconditionRequiredResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetJobBadRequestResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "BadRequest" error. +type GetJobBadRequestResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetJobConflictResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "Conflict" error. +type GetJobConflictResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetJobServiceUnavailableResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetJobServiceUnavailableResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetJobInternalServerErrorResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "InternalServerError" error. +type GetJobInternalServerErrorResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// GetJobNotFoundResponseBody is the type of the +// "lfx-v2-campaign-service-briefs" service "get-job" endpoint HTTP response +// body for the "NotFound" error. +type GetJobNotFoundResponseBody struct { + // HTTP status code + Code string `form:"code" json:"code" xml:"code"` + // Error message + Message string `form:"message" json:"message" xml:"message"` +} + +// PlatformResultResponseBody is used to define fields on response body types. +type PlatformResultResponseBody struct { + // Platform this result is for + Platform string `form:"platform" json:"platform" xml:"platform"` + // Whether the campaign was created (or reused) successfully + OK bool `form:"ok" json:"ok" xml:"ok"` + // Upstream platform campaign id. Present when ok; also set on the specific + // failure where the upstream campaign was created but recording it failed, so + // the orphaned id isn't lost. + CampaignID *string `form:"campaign_id,omitempty" json:"campaign_id,omitempty" xml:"campaign_id,omitempty"` + // Failure reason (present when not ok) + Error *string `form:"error,omitempty" json:"error,omitempty" xml:"error,omitempty"` +} + +// BriefInputRequestBody is used to define fields on request body types. +type BriefInputRequestBody struct { + // Funnel context + ProgramType *string `form:"program_type,omitempty" json:"program_type,omitempty" xml:"program_type,omitempty"` + // Event/course slug (unique within the project) + EventSlug *string `form:"event_slug,omitempty" json:"event_slug,omitempty" xml:"event_slug,omitempty"` + // Event/course page URL + URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Extracted event/course details + EventDetails any `form:"event_details,omitempty" json:"event_details,omitempty" xml:"event_details,omitempty"` + // Ad copy + Copy any `form:"copy,omitempty" json:"copy,omitempty" xml:"copy,omitempty"` + // Keyword list + Keywords any `form:"keywords,omitempty" json:"keywords,omitempty" xml:"keywords,omitempty"` + // Targeting recommendation + Targeting any `form:"targeting,omitempty" json:"targeting,omitempty" xml:"targeting,omitempty"` +} + +// CampaignCreateInputRequestBody is used to define fields on request body +// types. +type CampaignCreateInputRequestBody struct { + // Platforms to create campaigns on (binding selection) + Platforms []string `form:"platforms,omitempty" json:"platforms,omitempty" xml:"platforms,omitempty"` + // Per-platform campaign configuration + Config any `form:"config,omitempty" json:"config,omitempty" xml:"config,omitempty"` +} + +// CampaignUpdateInputRequestBody is used to define fields on request body +// types. +type CampaignUpdateInputRequestBody struct { + // Campaign name + CampaignName *string `form:"campaign_name,omitempty" json:"campaign_name,omitempty" xml:"campaign_name,omitempty"` + // Campaign status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Campaign configuration snapshot + Config any `form:"config,omitempty" json:"config,omitempty" xml:"config,omitempty"` +} + +// NewCreateBriefResponseBody builds the HTTP response body from the result of +// the "create-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefResponseBody(res *lfxv2campaignservicebriefs.Brief) *CreateBriefResponseBody { + body := &CreateBriefResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + ProgramType: res.ProgramType, + EventSlug: res.EventSlug, + URL: res.URL, + EventDetails: res.EventDetails, + Copy: res.Copy, + Keywords: res.Keywords, + Targeting: res.Targeting, + Status: res.Status, + Version: res.Version, + } + if res.Platforms != nil { + body.Platforms = make([]string, len(res.Platforms)) + for i, val := range res.Platforms { + body.Platforms[i] = val + } + } + return body +} + +// NewGetBriefResponseBody builds the HTTP response body from the result of the +// "get-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewGetBriefResponseBody(res *lfxv2campaignservicebriefs.Brief) *GetBriefResponseBody { + body := &GetBriefResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + ProgramType: res.ProgramType, + EventSlug: res.EventSlug, + URL: res.URL, + EventDetails: res.EventDetails, + Copy: res.Copy, + Keywords: res.Keywords, + Targeting: res.Targeting, + Status: res.Status, + Version: res.Version, + } + if res.Platforms != nil { + body.Platforms = make([]string, len(res.Platforms)) + for i, val := range res.Platforms { + body.Platforms[i] = val + } + } + return body +} + +// NewUpdateBriefResponseBody builds the HTTP response body from the result of +// the "update-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefResponseBody(res *lfxv2campaignservicebriefs.Brief) *UpdateBriefResponseBody { + body := &UpdateBriefResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + ProgramType: res.ProgramType, + EventSlug: res.EventSlug, + URL: res.URL, + EventDetails: res.EventDetails, + Copy: res.Copy, + Keywords: res.Keywords, + Targeting: res.Targeting, + Status: res.Status, + Version: res.Version, + } + if res.Platforms != nil { + body.Platforms = make([]string, len(res.Platforms)) + for i, val := range res.Platforms { + body.Platforms[i] = val + } + } + return body +} + +// NewApproveBriefResponseBody builds the HTTP response body from the result of +// the "approve-brief" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefResponseBody(res *lfxv2campaignservicebriefs.Brief) *ApproveBriefResponseBody { + body := &ApproveBriefResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + ProgramType: res.ProgramType, + EventSlug: res.EventSlug, + URL: res.URL, + EventDetails: res.EventDetails, + Copy: res.Copy, + Keywords: res.Keywords, + Targeting: res.Targeting, + Status: res.Status, + Version: res.Version, + } + if res.Platforms != nil { + body.Platforms = make([]string, len(res.Platforms)) + for i, val := range res.Platforms { + body.Platforms[i] = val + } + } + return body +} + +// NewCreateCampaignsResponseBody builds the HTTP response body from the result +// of the "create-campaigns" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewCreateCampaignsResponseBody(res *lfxv2campaignservicebriefs.JobCreateResponse) *CreateCampaignsResponseBody { + body := &CreateCampaignsResponseBody{ + JobID: res.JobID, + Status: res.Status, + } + if res.Platforms != nil { + body.Platforms = make([]string, len(res.Platforms)) + for i, val := range res.Platforms { + body.Platforms[i] = val + } + } else { + body.Platforms = []string{} + } + return body +} + +// NewGetCampaignResponseBody builds the HTTP response body from the result of +// the "get-campaign" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignResponseBody(res *lfxv2campaignservicebriefs.Campaign) *GetCampaignResponseBody { + body := &GetCampaignResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + BriefID: res.BriefID, + Platform: res.Platform, + PlatformCampaignID: res.PlatformCampaignID, + CampaignName: res.CampaignName, + Status: res.Status, + Version: res.Version, + } + return body +} + +// NewUpdateCampaignResponseBody builds the HTTP response body from the result +// of the "update-campaign" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewUpdateCampaignResponseBody(res *lfxv2campaignservicebriefs.Campaign) *UpdateCampaignResponseBody { + body := &UpdateCampaignResponseBody{ + ID: res.ID, + ProjectID: res.ProjectID, + BriefID: res.BriefID, + Platform: res.Platform, + PlatformCampaignID: res.PlatformCampaignID, + CampaignName: res.CampaignName, + Status: res.Status, + Version: res.Version, + } + return body +} + +// NewGetJobResponseBody builds the HTTP response body from the result of the +// "get-job" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewGetJobResponseBody(res *lfxv2campaignservicebriefs.JobPollResponse) *GetJobResponseBody { + body := &GetJobResponseBody{ + JobID: res.JobID, + Status: res.Status, + Error: res.Error, + } + if res.Result != nil { + body.Result = make([]*PlatformResultResponseBody, len(res.Result)) + for i, val := range res.Result { + if val == nil { + body.Result[i] = nil + continue + } + body.Result[i] = marshalLfxv2campaignservicebriefsPlatformResultToPlatformResultResponseBody(val) + } + } + return body +} + +// NewCreateBriefBadRequestResponseBody builds the HTTP response body from the +// result of the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *CreateBriefBadRequestResponseBody { + body := &CreateBriefBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateBriefConflictResponseBody builds the HTTP response body from the +// result of the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *CreateBriefConflictResponseBody { + body := &CreateBriefConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateBriefServiceUnavailableResponseBody builds the HTTP response body +// from the result of the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *CreateBriefServiceUnavailableResponseBody { + body := &CreateBriefServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateBriefInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *CreateBriefInternalServerErrorResponseBody { + body := &CreateBriefInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateBriefNotFoundResponseBody builds the HTTP response body from the +// result of the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateBriefNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *CreateBriefNotFoundResponseBody { + body := &CreateBriefNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetBriefBadRequestResponseBody builds the HTTP response body from the +// result of the "get-brief" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetBriefBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *GetBriefBadRequestResponseBody { + body := &GetBriefBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetBriefConflictResponseBody builds the HTTP response body from the +// result of the "get-brief" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetBriefConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *GetBriefConflictResponseBody { + body := &GetBriefConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetBriefServiceUnavailableResponseBody builds the HTTP response body from +// the result of the "get-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetBriefServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *GetBriefServiceUnavailableResponseBody { + body := &GetBriefServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetBriefInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "get-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetBriefInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *GetBriefInternalServerErrorResponseBody { + body := &GetBriefInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetBriefNotFoundResponseBody builds the HTTP response body from the +// result of the "get-brief" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetBriefNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *GetBriefNotFoundResponseBody { + body := &GetBriefNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefBadRequestResponseBody builds the HTTP response body from the +// result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *UpdateBriefBadRequestResponseBody { + body := &UpdateBriefBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefConflictResponseBody builds the HTTP response body from the +// result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *UpdateBriefConflictResponseBody { + body := &UpdateBriefConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefServiceUnavailableResponseBody builds the HTTP response body +// from the result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *UpdateBriefServiceUnavailableResponseBody { + body := &UpdateBriefServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *UpdateBriefInternalServerErrorResponseBody { + body := &UpdateBriefInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefNotFoundResponseBody builds the HTTP response body from the +// result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *UpdateBriefNotFoundResponseBody { + body := &UpdateBriefNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefPreconditionFailedResponseBody builds the HTTP response body +// from the result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefPreconditionFailedResponseBody(res *lfxv2campaignservicebriefs.PreconditionFailedError) *UpdateBriefPreconditionFailedResponseBody { + body := &UpdateBriefPreconditionFailedResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateBriefPreconditionRequiredResponseBody builds the HTTP response body +// from the result of the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateBriefPreconditionRequiredResponseBody(res *lfxv2campaignservicebriefs.PreconditionRequiredError) *UpdateBriefPreconditionRequiredResponseBody { + body := &UpdateBriefPreconditionRequiredResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefBadRequestResponseBody builds the HTTP response body from the +// result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *ApproveBriefBadRequestResponseBody { + body := &ApproveBriefBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefConflictResponseBody builds the HTTP response body from the +// result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *ApproveBriefConflictResponseBody { + body := &ApproveBriefConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefServiceUnavailableResponseBody builds the HTTP response body +// from the result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *ApproveBriefServiceUnavailableResponseBody { + body := &ApproveBriefServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *ApproveBriefInternalServerErrorResponseBody { + body := &ApproveBriefInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefNotFoundResponseBody builds the HTTP response body from the +// result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *ApproveBriefNotFoundResponseBody { + body := &ApproveBriefNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefPreconditionFailedResponseBody builds the HTTP response body +// from the result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefPreconditionFailedResponseBody(res *lfxv2campaignservicebriefs.PreconditionFailedError) *ApproveBriefPreconditionFailedResponseBody { + body := &ApproveBriefPreconditionFailedResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewApproveBriefPreconditionRequiredResponseBody builds the HTTP response +// body from the result of the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewApproveBriefPreconditionRequiredResponseBody(res *lfxv2campaignservicebriefs.PreconditionRequiredError) *ApproveBriefPreconditionRequiredResponseBody { + body := &ApproveBriefPreconditionRequiredResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewDeleteBriefBadRequestResponseBody builds the HTTP response body from the +// result of the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewDeleteBriefBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *DeleteBriefBadRequestResponseBody { + body := &DeleteBriefBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewDeleteBriefConflictResponseBody builds the HTTP response body from the +// result of the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewDeleteBriefConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *DeleteBriefConflictResponseBody { + body := &DeleteBriefConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewDeleteBriefServiceUnavailableResponseBody builds the HTTP response body +// from the result of the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewDeleteBriefServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *DeleteBriefServiceUnavailableResponseBody { + body := &DeleteBriefServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewDeleteBriefInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewDeleteBriefInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *DeleteBriefInternalServerErrorResponseBody { + body := &DeleteBriefInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewDeleteBriefNotFoundResponseBody builds the HTTP response body from the +// result of the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewDeleteBriefNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *DeleteBriefNotFoundResponseBody { + body := &DeleteBriefNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateCampaignsBadRequestResponseBody builds the HTTP response body from +// the result of the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateCampaignsBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *CreateCampaignsBadRequestResponseBody { + body := &CreateCampaignsBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateCampaignsConflictResponseBody builds the HTTP response body from +// the result of the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateCampaignsConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *CreateCampaignsConflictResponseBody { + body := &CreateCampaignsConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateCampaignsServiceUnavailableResponseBody builds the HTTP response +// body from the result of the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateCampaignsServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *CreateCampaignsServiceUnavailableResponseBody { + body := &CreateCampaignsServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateCampaignsInternalServerErrorResponseBody builds the HTTP response +// body from the result of the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateCampaignsInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *CreateCampaignsInternalServerErrorResponseBody { + body := &CreateCampaignsInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateCampaignsNotFoundResponseBody builds the HTTP response body from +// the result of the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewCreateCampaignsNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *CreateCampaignsNotFoundResponseBody { + body := &CreateCampaignsNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetCampaignBadRequestResponseBody builds the HTTP response body from the +// result of the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *GetCampaignBadRequestResponseBody { + body := &GetCampaignBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetCampaignConflictResponseBody builds the HTTP response body from the +// result of the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *GetCampaignConflictResponseBody { + body := &GetCampaignConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetCampaignServiceUnavailableResponseBody builds the HTTP response body +// from the result of the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *GetCampaignServiceUnavailableResponseBody { + body := &GetCampaignServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetCampaignInternalServerErrorResponseBody builds the HTTP response body +// from the result of the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *GetCampaignInternalServerErrorResponseBody { + body := &GetCampaignInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetCampaignNotFoundResponseBody builds the HTTP response body from the +// result of the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewGetCampaignNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *GetCampaignNotFoundResponseBody { + body := &GetCampaignNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignBadRequestResponseBody builds the HTTP response body from +// the result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *UpdateCampaignBadRequestResponseBody { + body := &UpdateCampaignBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignConflictResponseBody builds the HTTP response body from the +// result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *UpdateCampaignConflictResponseBody { + body := &UpdateCampaignConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignServiceUnavailableResponseBody builds the HTTP response +// body from the result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *UpdateCampaignServiceUnavailableResponseBody { + body := &UpdateCampaignServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignInternalServerErrorResponseBody builds the HTTP response +// body from the result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *UpdateCampaignInternalServerErrorResponseBody { + body := &UpdateCampaignInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignNotFoundResponseBody builds the HTTP response body from the +// result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *UpdateCampaignNotFoundResponseBody { + body := &UpdateCampaignNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignPreconditionFailedResponseBody builds the HTTP response +// body from the result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignPreconditionFailedResponseBody(res *lfxv2campaignservicebriefs.PreconditionFailedError) *UpdateCampaignPreconditionFailedResponseBody { + body := &UpdateCampaignPreconditionFailedResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewUpdateCampaignPreconditionRequiredResponseBody builds the HTTP response +// body from the result of the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +func NewUpdateCampaignPreconditionRequiredResponseBody(res *lfxv2campaignservicebriefs.PreconditionRequiredError) *UpdateCampaignPreconditionRequiredResponseBody { + body := &UpdateCampaignPreconditionRequiredResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetJobBadRequestResponseBody builds the HTTP response body from the +// result of the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetJobBadRequestResponseBody(res *lfxv2campaignservicebriefs.BadRequestError) *GetJobBadRequestResponseBody { + body := &GetJobBadRequestResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetJobConflictResponseBody builds the HTTP response body from the result +// of the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewGetJobConflictResponseBody(res *lfxv2campaignservicebriefs.ConflictError) *GetJobConflictResponseBody { + body := &GetJobConflictResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetJobServiceUnavailableResponseBody builds the HTTP response body from +// the result of the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetJobServiceUnavailableResponseBody(res *lfxv2campaignservicebriefs.ConnServiceUnavailableError) *GetJobServiceUnavailableResponseBody { + body := &GetJobServiceUnavailableResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetJobInternalServerErrorResponseBody builds the HTTP response body from +// the result of the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +func NewGetJobInternalServerErrorResponseBody(res *lfxv2campaignservicebriefs.InternalServerError) *GetJobInternalServerErrorResponseBody { + body := &GetJobInternalServerErrorResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewGetJobNotFoundResponseBody builds the HTTP response body from the result +// of the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" service. +func NewGetJobNotFoundResponseBody(res *lfxv2campaignservicebriefs.NotFoundError) *GetJobNotFoundResponseBody { + body := &GetJobNotFoundResponseBody{ + Code: res.Code, + Message: res.Message, + } + return body +} + +// NewCreateBriefPayload builds a lfx-v2-campaign-service-briefs service +// create-brief endpoint payload. +func NewCreateBriefPayload(body *CreateBriefRequestBody, projectID string, bearerToken *string) *lfxv2campaignservicebriefs.CreateBriefPayload { + v := &lfxv2campaignservicebriefs.CreateBriefPayload{} + v.Brief = unmarshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(body.Brief) + v.ProjectID = projectID + v.BearerToken = bearerToken + + return v +} + +// NewGetBriefPayload builds a lfx-v2-campaign-service-briefs service get-brief +// endpoint payload. +func NewGetBriefPayload(projectID string, briefID string, bearerToken *string) *lfxv2campaignservicebriefs.GetBriefPayload { + v := &lfxv2campaignservicebriefs.GetBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v +} + +// NewUpdateBriefPayload builds a lfx-v2-campaign-service-briefs service +// update-brief endpoint payload. +func NewUpdateBriefPayload(body *UpdateBriefRequestBody, projectID string, briefID string, bearerToken *string, ifMatch *string) *lfxv2campaignservicebriefs.UpdateBriefPayload { + v := &lfxv2campaignservicebriefs.UpdateBriefPayload{} + v.Brief = unmarshalBriefInputRequestBodyToLfxv2campaignservicebriefsBriefInput(body.Brief) + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v +} + +// NewApproveBriefPayload builds a lfx-v2-campaign-service-briefs service +// approve-brief endpoint payload. +func NewApproveBriefPayload(projectID string, briefID string, bearerToken *string, ifMatch *string) *lfxv2campaignservicebriefs.ApproveBriefPayload { + v := &lfxv2campaignservicebriefs.ApproveBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v +} + +// NewDeleteBriefPayload builds a lfx-v2-campaign-service-briefs service +// delete-brief endpoint payload. +func NewDeleteBriefPayload(projectID string, briefID string, bearerToken *string) *lfxv2campaignservicebriefs.DeleteBriefPayload { + v := &lfxv2campaignservicebriefs.DeleteBriefPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v +} + +// NewCreateCampaignsPayload builds a lfx-v2-campaign-service-briefs service +// create-campaigns endpoint payload. +func NewCreateCampaignsPayload(body *CreateCampaignsRequestBody, projectID string, briefID string, bearerToken *string) *lfxv2campaignservicebriefs.CreateCampaignsPayload { + v := &lfxv2campaignservicebriefs.CreateCampaignsPayload{} + v.Input = unmarshalCampaignCreateInputRequestBodyToLfxv2campaignservicebriefsCampaignCreateInput(body.Input) + v.ProjectID = projectID + v.BriefID = briefID + v.BearerToken = bearerToken + + return v +} + +// NewGetCampaignPayload builds a lfx-v2-campaign-service-briefs service +// get-campaign endpoint payload. +func NewGetCampaignPayload(projectID string, briefID string, campaignID string, bearerToken *string) *lfxv2campaignservicebriefs.GetCampaignPayload { + v := &lfxv2campaignservicebriefs.GetCampaignPayload{} + v.ProjectID = projectID + v.BriefID = briefID + v.CampaignID = campaignID + v.BearerToken = bearerToken + + return v +} + +// NewUpdateCampaignPayload builds a lfx-v2-campaign-service-briefs service +// update-campaign endpoint payload. +func NewUpdateCampaignPayload(body *UpdateCampaignRequestBody, projectID string, briefID string, campaignID string, bearerToken *string, ifMatch *string) *lfxv2campaignservicebriefs.UpdateCampaignPayload { + v := &lfxv2campaignservicebriefs.UpdateCampaignPayload{} + v.Campaign = unmarshalCampaignUpdateInputRequestBodyToLfxv2campaignservicebriefsCampaignUpdateInput(body.Campaign) + v.ProjectID = projectID + v.BriefID = briefID + v.CampaignID = campaignID + v.BearerToken = bearerToken + v.IfMatch = ifMatch + + return v +} + +// NewGetJobPayload builds a lfx-v2-campaign-service-briefs service get-job +// endpoint payload. +func NewGetJobPayload(projectID string, jobID string, bearerToken *string) *lfxv2campaignservicebriefs.GetJobPayload { + v := &lfxv2campaignservicebriefs.GetJobPayload{} + v.ProjectID = projectID + v.JobID = jobID + v.BearerToken = bearerToken + + return v +} + +// ValidateCreateBriefRequestBody runs the validations defined on +// Create-BriefRequestBody +func ValidateCreateBriefRequestBody(body *CreateBriefRequestBody) (err error) { + if body.Brief == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief", "body")) + } + if body.Brief != nil { + if err2 := ValidateBriefInputRequestBody(body.Brief); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateUpdateBriefRequestBody runs the validations defined on +// Update-BriefRequestBody +func ValidateUpdateBriefRequestBody(body *UpdateBriefRequestBody) (err error) { + if body.Brief == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("brief", "body")) + } + if body.Brief != nil { + if err2 := ValidateBriefInputRequestBody(body.Brief); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateCreateCampaignsRequestBody runs the validations defined on +// Create-CampaignsRequestBody +func ValidateCreateCampaignsRequestBody(body *CreateCampaignsRequestBody) (err error) { + if body.Input == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("input", "body")) + } + if body.Input != nil { + if err2 := ValidateCampaignCreateInputRequestBody(body.Input); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateUpdateCampaignRequestBody runs the validations defined on +// Update-CampaignRequestBody +func ValidateUpdateCampaignRequestBody(body *UpdateCampaignRequestBody) (err error) { + if body.Campaign == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("campaign", "body")) + } + if body.Campaign != nil { + if err2 := ValidateCampaignUpdateInputRequestBody(body.Campaign); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateBriefInputRequestBody runs the validations defined on +// brief-inputRequestBody +func ValidateBriefInputRequestBody(body *BriefInputRequestBody) (err error) { + if body.ProgramType == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("program_type", "body")) + } + if body.EventSlug == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event_slug", "body")) + } + if body.ProgramType != nil { + if !(*body.ProgramType == "events" || *body.ProgramType == "education" || *body.ProgramType == "membership") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.program_type", *body.ProgramType, []any{"events", "education", "membership"})) + } + } + return +} + +// ValidateCampaignCreateInputRequestBody runs the validations defined on +// campaign-create-inputRequestBody +func ValidateCampaignCreateInputRequestBody(body *CampaignCreateInputRequestBody) (err error) { + if body.Platforms == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("platforms", "body")) + } + if len(body.Platforms) < 1 { + err = goa.MergeErrors(err, goa.InvalidLengthError("body.platforms", body.Platforms, len(body.Platforms), 1, true)) + } + for _, e := range body.Platforms { + if !(e == "google-ads" || e == "linkedin-ads" || e == "meta-ads" || e == "reddit-ads" || e == "twitter-ads" || e == "microsoft-ads" || e == "hubspot") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.platforms[*]", e, []any{"google-ads", "linkedin-ads", "meta-ads", "reddit-ads", "twitter-ads", "microsoft-ads", "hubspot"})) + } + } + return +} + +// ValidateCampaignUpdateInputRequestBody runs the validations defined on +// campaign-update-inputRequestBody +func ValidateCampaignUpdateInputRequestBody(body *CampaignUpdateInputRequestBody) (err error) { + if body.CampaignName == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("campaign_name", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + return +} diff --git a/gen/http/lfx_v2_campaign_service_connections/client/cli.go b/gen/http/lfx_v2_campaign_service_connections/client/cli.go index 16abaefc..d81cde18 100644 --- a/gen/http/lfx_v2_campaign_service_connections/client/cli.go +++ b/gen/http/lfx_v2_campaign_service_connections/client/cli.go @@ -24,7 +24,7 @@ func BuildCreateGoogleAdsPayload(lfxV2CampaignServiceConnectionsCreateGoogleAdsB { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateGoogleAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Recusandae nam autem dicta perspiciatis quod.\",\n \"client_secret\": \"Quo voluptatem quas culpa omnis consequuntur iste.\",\n \"developer_token\": \"Id magnam id nihil fugiat.\",\n \"refresh_token\": \"Inventore quisquam laudantium velit.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8666746580\",\n \"label\": \"TLF Main\",\n \"login_customer_id\": \"9746983954\"\n },\n \"credentials\": {\n \"client_id\": \"Architecto itaque doloribus voluptatem consequatur quis fugiat.\",\n \"client_secret\": \"Recusandae qui culpa.\",\n \"developer_token\": \"Accusamus et atque enim.\",\n \"refresh_token\": \"Dolorum minus voluptatem.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -174,7 +174,7 @@ func BuildSetCredentialGoogleAdsPayload(lfxV2CampaignServiceConnectionsSetCreden { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialGoogleAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Recusandae nam autem dicta perspiciatis quod.\",\n \"client_secret\": \"Quo voluptatem quas culpa omnis consequuntur iste.\",\n \"developer_token\": \"Id magnam id nihil fugiat.\",\n \"refresh_token\": \"Inventore quisquam laudantium velit.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Architecto itaque doloribus voluptatem consequatur quis fugiat.\",\n \"client_secret\": \"Recusandae qui culpa.\",\n \"developer_token\": \"Accusamus et atque enim.\",\n \"refresh_token\": \"Dolorum minus voluptatem.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -212,7 +212,7 @@ func BuildCreateLinkedinAdsPayload(lfxV2CampaignServiceConnectionsCreateLinkedin { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateLinkedinAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Fugit voluptatem.\",\n \"org_id\": \"208777\"\n },\n \"credentials\": {\n \"access_token\": \"Amet necessitatibus corrupti quaerat molestiae maiores.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Temporibus et provident maiores neque.\",\n \"org_id\": \"208777\"\n },\n \"credentials\": {\n \"access_token\": \"Illo minima eaque earum.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -276,7 +276,7 @@ func BuildUpdateLinkedinAdsPayload(lfxV2CampaignServiceConnectionsUpdateLinkedin { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateLinkedinAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Fugit voluptatem.\",\n \"org_id\": \"208777\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"538170226\",\n \"label\": \"Temporibus et provident maiores neque.\",\n \"org_id\": \"208777\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -363,7 +363,7 @@ func BuildSetCredentialLinkedinAdsPayload(lfxV2CampaignServiceConnectionsSetCred { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Amet necessitatibus corrupti quaerat molestiae maiores.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Illo minima eaque earum.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -400,7 +400,7 @@ func BuildCreateMetaAdsPayload(lfxV2CampaignServiceConnectionsCreateMetaAdsBody { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateMetaAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Eligendi vero temporibus.\",\n \"label\": \"Non maiores quae sed quia.\",\n \"page_id\": \"Facilis eum ipsam.\"\n },\n \"credentials\": {\n \"access_token\": \"Soluta quas voluptas minus possimus quos praesentium.\",\n \"app_secret\": \"Repudiandae consequatur fuga dolores.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Qui rerum est sed sed.\",\n \"label\": \"Quis quam dolor occaecati earum iusto rerum.\",\n \"page_id\": \"Cupiditate nobis.\"\n },\n \"credentials\": {\n \"access_token\": \"Enim ea.\",\n \"app_secret\": \"Quidem molestiae deleniti omnis.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -463,7 +463,7 @@ func BuildUpdateMetaAdsPayload(lfxV2CampaignServiceConnectionsUpdateMetaAdsBody { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateMetaAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Eligendi vero temporibus.\",\n \"label\": \"Non maiores quae sed quia.\",\n \"page_id\": \"Facilis eum ipsam.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"act_193556282970417\",\n \"app_id\": \"Qui rerum est sed sed.\",\n \"label\": \"Quis quam dolor occaecati earum iusto rerum.\",\n \"page_id\": \"Cupiditate nobis.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -548,7 +548,7 @@ func BuildSetCredentialMetaAdsPayload(lfxV2CampaignServiceConnectionsSetCredenti { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialMetaAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Soluta quas voluptas minus possimus quos praesentium.\",\n \"app_secret\": \"Repudiandae consequatur fuga dolores.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Enim ea.\",\n \"app_secret\": \"Quidem molestiae deleniti omnis.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -586,7 +586,7 @@ func BuildCreateRedditAdsPayload(lfxV2CampaignServiceConnectionsCreateRedditAdsB { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateRedditAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Ut est nobis aspernatur.\"\n },\n \"credentials\": {\n \"client_id\": \"Fugit qui tempora commodi reiciendis sit alias.\",\n \"client_secret\": \"Ea enim quidem odio et sequi consequatur.\",\n \"refresh_token\": \"Provident itaque.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Laboriosam qui id enim.\"\n },\n \"credentials\": {\n \"client_id\": \"Magni repudiandae.\",\n \"client_secret\": \"Nihil corrupti repellat eius.\",\n \"refresh_token\": \"Qui nobis et delectus quam.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -650,7 +650,7 @@ func BuildUpdateRedditAdsPayload(lfxV2CampaignServiceConnectionsUpdateRedditAdsB { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateRedditAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Ut est nobis aspernatur.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"t2_gv9wtbfa\",\n \"label\": \"Laboriosam qui id enim.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -736,7 +736,7 @@ func BuildSetCredentialRedditAdsPayload(lfxV2CampaignServiceConnectionsSetCreden { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialRedditAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Fugit qui tempora commodi reiciendis sit alias.\",\n \"client_secret\": \"Ea enim quidem odio et sequi consequatur.\",\n \"refresh_token\": \"Provident itaque.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Magni repudiandae.\",\n \"client_secret\": \"Nihil corrupti repellat eius.\",\n \"refresh_token\": \"Qui nobis et delectus quam.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -774,7 +774,7 @@ func BuildCreateTwitterAdsPayload(lfxV2CampaignServiceConnectionsCreateTwitterAd { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateTwitterAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Eveniet dicta numquam doloremque.\",\n \"label\": \"Ea velit et assumenda recusandae enim.\"\n },\n \"credentials\": {\n \"access_token\": \"Ipsum quia ad a.\",\n \"access_token_secret\": \"Veritatis omnis excepturi.\",\n \"consumer_key\": \"Cumque libero.\",\n \"consumer_secret\": \"Id incidunt qui et ratione sit quaerat.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Dolorem non laudantium sunt minima repudiandae illo.\",\n \"label\": \"Labore esse.\"\n },\n \"credentials\": {\n \"access_token\": \"Deleniti placeat fuga aut saepe.\",\n \"access_token_secret\": \"Reiciendis pariatur repellat.\",\n \"consumer_key\": \"Nulla dolorum deserunt ut sit dolor.\",\n \"consumer_secret\": \"Eos nihil ipsam ex odio totam.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -838,7 +838,7 @@ func BuildUpdateTwitterAdsPayload(lfxV2CampaignServiceConnectionsUpdateTwitterAd { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateTwitterAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Eveniet dicta numquam doloremque.\",\n \"label\": \"Ea velit et assumenda recusandae enim.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"8r7gb\",\n \"funding_instrument_id\": \"Dolorem non laudantium sunt minima repudiandae illo.\",\n \"label\": \"Labore esse.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -924,7 +924,7 @@ func BuildSetCredentialTwitterAdsPayload(lfxV2CampaignServiceConnectionsSetCrede { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialTwitterAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Ipsum quia ad a.\",\n \"access_token_secret\": \"Veritatis omnis excepturi.\",\n \"consumer_key\": \"Cumque libero.\",\n \"consumer_secret\": \"Id incidunt qui et ratione sit quaerat.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"access_token\": \"Deleniti placeat fuga aut saepe.\",\n \"access_token_secret\": \"Reiciendis pariatur repellat.\",\n \"consumer_key\": \"Nulla dolorum deserunt ut sit dolor.\",\n \"consumer_secret\": \"Eos nihil ipsam ex odio totam.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -962,7 +962,7 @@ func BuildCreateMicrosoftAdsPayload(lfxV2CampaignServiceConnectionsCreateMicroso { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateMicrosoftAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Sint qui praesentium ea praesentium magni.\",\n \"customer_id\": \"Iure dolore et et aut nam.\",\n \"label\": \"Consequuntur neque mollitia quia.\"\n },\n \"credentials\": {\n \"client_id\": \"Voluptates odit consequatur quis.\",\n \"client_secret\": \"Est voluptatum enim earum et doloribus.\",\n \"developer_token\": \"Id ducimus eius et laboriosam omnis vitae.\",\n \"refresh_token\": \"Ea consequatur et ut.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Ut necessitatibus dolor nemo.\",\n \"customer_id\": \"Nisi numquam nihil id unde.\",\n \"label\": \"Voluptatem sed illo numquam praesentium tenetur quas.\"\n },\n \"credentials\": {\n \"client_id\": \"Officiis aut fugiat optio ut.\",\n \"client_secret\": \"Et voluptas tenetur nobis facere.\",\n \"developer_token\": \"Blanditiis mollitia dolores illo nostrum magnam.\",\n \"refresh_token\": \"Quo corporis recusandae dicta quibusdam.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -1027,7 +1027,7 @@ func BuildUpdateMicrosoftAdsPayload(lfxV2CampaignServiceConnectionsUpdateMicroso { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateMicrosoftAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Sint qui praesentium ea praesentium magni.\",\n \"customer_id\": \"Iure dolore et et aut nam.\",\n \"label\": \"Consequuntur neque mollitia quia.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Ut necessitatibus dolor nemo.\",\n \"customer_id\": \"Nisi numquam nihil id unde.\",\n \"label\": \"Voluptatem sed illo numquam praesentium tenetur quas.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -1114,7 +1114,7 @@ func BuildSetCredentialMicrosoftAdsPayload(lfxV2CampaignServiceConnectionsSetCre { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Voluptates odit consequatur quis.\",\n \"client_secret\": \"Est voluptatum enim earum et doloribus.\",\n \"developer_token\": \"Id ducimus eius et laboriosam omnis vitae.\",\n \"refresh_token\": \"Ea consequatur et ut.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"client_id\": \"Officiis aut fugiat optio ut.\",\n \"client_secret\": \"Et voluptas tenetur nobis facere.\",\n \"developer_token\": \"Blanditiis mollitia dolores illo nostrum magnam.\",\n \"refresh_token\": \"Quo corporis recusandae dicta quibusdam.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) @@ -1151,7 +1151,7 @@ func BuildCreateHubspotPayload(lfxV2CampaignServiceConnectionsCreateHubspotBody { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsCreateHubspotBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Temporibus voluptas inventore sit omnis.\",\n \"brand_kit\": \"Quis facere sint totam sapiente.\",\n \"label\": \"Sint nihil.\",\n \"portal_id\": \"Expedita veniam beatae autem.\",\n \"sender_email\": \"Placeat enim.\",\n \"sender_name\": \"Nesciunt asperiores et est.\"\n },\n \"credentials\": {\n \"private_app_token\": \"Numquam ut nesciunt voluptas.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Maxime dicta qui aut.\",\n \"brand_kit\": \"Corrupti voluptas.\",\n \"label\": \"Dolor porro inventore alias error.\",\n \"portal_id\": \"Suscipit eos nam ipsa.\",\n \"sender_email\": \"Velit et impedit facilis quaerat.\",\n \"sender_name\": \"Dolor ut velit quam omnis et dolor.\"\n },\n \"credentials\": {\n \"private_app_token\": \"Vitae vel omnis autem accusamus mollitia.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -1214,7 +1214,7 @@ func BuildUpdateHubspotPayload(lfxV2CampaignServiceConnectionsUpdateHubspotBody { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsUpdateHubspotBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Temporibus voluptas inventore sit omnis.\",\n \"brand_kit\": \"Quis facere sint totam sapiente.\",\n \"label\": \"Sint nihil.\",\n \"portal_id\": \"Expedita veniam beatae autem.\",\n \"sender_email\": \"Placeat enim.\",\n \"sender_name\": \"Nesciunt asperiores et est.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"config\": {\n \"account_id\": \"Maxime dicta qui aut.\",\n \"brand_kit\": \"Corrupti voluptas.\",\n \"label\": \"Dolor porro inventore alias error.\",\n \"portal_id\": \"Suscipit eos nam ipsa.\",\n \"sender_email\": \"Velit et impedit facilis quaerat.\",\n \"sender_name\": \"Dolor ut velit quam omnis et dolor.\"\n }\n }'") } if body.Config == nil { err = goa.MergeErrors(err, goa.MissingFieldError("config", "body")) @@ -1299,7 +1299,7 @@ func BuildSetCredentialHubspotPayload(lfxV2CampaignServiceConnectionsSetCredenti { err = json.Unmarshal([]byte(lfxV2CampaignServiceConnectionsSetCredentialHubspotBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"private_app_token\": \"Numquam ut nesciunt voluptas.\"\n }\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"credentials\": {\n \"private_app_token\": \"Vitae vel omnis autem accusamus mollitia.\"\n }\n }'") } if body.Credentials == nil { err = goa.MergeErrors(err, goa.MissingFieldError("credentials", "body")) diff --git a/gen/http/openapi.json b/gen/http/openapi.json index a91e4ac4..77a41b95 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/projects/{project_id}/connection-google-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"description":"A connection already exists for this provider on the project","example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"title":"ConnServiceUnavailableError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"title":"ConnectionTestResult","type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Labore aut eum porro."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":true}},"example":{"message":"Necessitatibus consequatur natus tempora ut.","ok":true},"required":["ok"]},"GoogleAdsConnection":{"title":"GoogleAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Aut voluptatem inventore ab."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quis et maxime ipsam voluptatem repudiandae laudantium."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis ipsam."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Iusto quis magni et velit."},"project_id":{"type":"string","description":"Owning project","example":"Ut ipsum id."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8470973010316083422,"format":"int64"}},"example":{"account_id":"Aut id molestiae sit.","has_credentials":true,"id":"Magni voluptas odio assumenda totam.","label":"Nam cumque.","login_customer_id":"Qui esse.","project_id":"Est praesentium dolor officiis deleniti.","status":"active","version":7862773009082385007},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"title":"GoogleAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"title":"GoogleAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ducimus sequi."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Autem ab voluptas culpa."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Quia tenetur ducimus."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Aut et."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Alias non sequi consequatur dolorum.","client_secret":"Voluptatem voluptas omnis est tempora ipsum aut.","developer_token":"Harum et minus voluptas.","refresh_token":"Facere harum enim consequatur id voluptatem."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"title":"HubspotConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Sed maiores qui sed exercitationem quam."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Asperiores facere et exercitationem soluta possimus."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"In expedita."},"label":{"type":"string","description":"Optional friendly name","example":"Suscipit sed minus soluta quasi."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Quia ducimus."},"project_id":{"type":"string","description":"Owning project","example":"Magnam cumque quasi magni ducimus."},"sender_email":{"type":"string","description":"Default sender address","example":"Et veritatis voluptas nobis."},"sender_name":{"type":"string","description":"Default sender name","example":"Doloremque architecto ea et."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4054743500330389510,"format":"int64"}},"example":{"account_id":"Est iste.","brand_kit":"Quae aut culpa impedit sit.","has_credentials":true,"id":"Aliquid et in.","label":"Distinctio aut.","portal_id":"Earum modi esse.","project_id":"Consequuntur omnis a dignissimos voluptate.","sender_email":"Possimus placeat enim ab.","sender_name":"Esse et qui et voluptatibus tempore.","status":"active","version":3449749392517333935},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"title":"HubspotConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Velit labore non."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Dolorem sapiente."},"label":{"type":"string","description":"Optional friendly name","example":"Nulla adipisci aperiam dolorem molestias."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Odit est esse."},"sender_email":{"type":"string","description":"Default sender address","example":"Ea cum et magni adipisci magnam."},"sender_name":{"type":"string","description":"Default sender name","example":"Atque labore qui odio et corporis."}},"example":{"account_id":"Sint autem.","brand_kit":"Debitis soluta sed quod dicta aperiam eveniet.","label":"Aut natus.","portal_id":"Deleniti itaque voluptas magnam.","sender_email":"Voluptatem modi velit vitae optio ut.","sender_name":"Consequuntur sit libero autem ut."},"required":["account_id"]},"HubspotCredentials":{"title":"HubspotCredentials","type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Dolor id."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Accusantium sequi modi corrupti esse mollitia."},"required":["private_app_token"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"},"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}},"required":["config"]},"LinkedinAdsConnection":{"title":"LinkedinAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vel blanditiis sunt ut facere."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsa nihil ad hic repellendus nobis voluptatem."},"label":{"type":"string","description":"Optional friendly name","example":"Rerum cupiditate."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Vel molestiae consequatur."},"project_id":{"type":"string","description":"Owning project","example":"Vero et voluptas accusamus sunt."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4079703323268843140,"format":"int64"}},"example":{"account_id":"Et corporis officia sint.","has_credentials":false,"id":"Quidem est non tenetur dolore.","label":"Cupiditate voluptatem.","org_id":"Quia nulla autem ratione.","project_id":"Dolores commodi aperiam voluptatibus.","status":"error","version":874315130556833044},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"title":"LinkedinAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"In mollitia a magni."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Consequatur ea deserunt tempora aut assumenda.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"title":"LinkedinAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Occaecati eum tenetur."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Inventore aliquid voluptatibus iste mollitia."},"required":["access_token"]},"MetaAdsConnection":{"title":"MetaAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Asperiores ipsum quam."},"app_id":{"type":"string","description":"Meta app ID","example":"Cum rem architecto vel quibusdam vel autem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Libero atque est non praesentium."},"label":{"type":"string","description":"Optional friendly name","example":"Id tenetur exercitationem aperiam incidunt."},"page_id":{"type":"string","description":"Facebook page ID","example":"Qui suscipit rerum quo eligendi tempora."},"project_id":{"type":"string","description":"Owning project","example":"Saepe sit et sed qui dolorem reiciendis."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5968285761891001183,"format":"int64"}},"example":{"account_id":"Quibusdam aliquam consequuntur.","app_id":"Deleniti earum magni quibusdam.","has_credentials":false,"id":"Id facere maxime.","label":"Maxime ut possimus expedita.","page_id":"Facilis facilis aut suscipit aut odio iste.","project_id":"Hic delectus.","status":"active","version":6674224742141168677},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"title":"MetaAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Harum provident quia distinctio ea."},"label":{"type":"string","description":"Optional friendly name","example":"Tenetur velit quaerat aut."},"page_id":{"type":"string","description":"Facebook page ID","example":"Aut et."}},"example":{"account_id":"act_193556282970417","app_id":"Blanditiis atque rerum et officiis sapiente et.","label":"Rem harum provident provident quia laboriosam magnam.","page_id":"Hic et sed."},"required":["account_id"]},"MetaAdsCredentials":{"title":"MetaAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Itaque dolores in hic rem."},"app_secret":{"type":"string","description":"Meta app secret","example":"Eligendi ea vel consequatur nihil."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Vero commodi est qui asperiores velit enim.","app_secret":"Expedita ut minus ut est recusandae aspernatur."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"title":"MicrosoftAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ipsum voluptatem et quam beatae laborum omnis."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Rem suscipit similique ipsam aut quis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Repudiandae voluptatem vitae iste maxime modi laborum."},"label":{"type":"string","description":"Optional friendly name","example":"Magnam fugit."},"project_id":{"type":"string","description":"Owning project","example":"Dignissimos necessitatibus consequuntur."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7512765750150729777,"format":"int64"}},"example":{"account_id":"Nobis nam.","customer_id":"Reiciendis pariatur quisquam.","has_credentials":true,"id":"Laudantium dolore.","label":"Non quidem doloribus debitis itaque itaque dolores.","project_id":"Possimus nihil qui enim ratione voluptatem eos.","status":"error","version":7616558197057713508},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"title":"MicrosoftAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Aspernatur facere."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Voluptatem dicta rerum sed."},"label":{"type":"string","description":"Optional friendly name","example":"Aspernatur ea voluptatem eum voluptas eveniet assumenda."}},"example":{"account_id":"Quia vero quis consequuntur id.","customer_id":"Quo illum cum quod.","label":"Quia possimus dolore vel qui."},"required":["account_id"]},"MicrosoftAdsCredentials":{"title":"MicrosoftAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ut fugiat magnam quia."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Ut tempora consequuntur cumque repudiandae omnis ut."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Est aut labore sint natus."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Distinctio beatae."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Ducimus debitis nesciunt quas quibusdam occaecati cum.","client_secret":"Iste voluptatem et voluptatem ut ut.","developer_token":"Quia sed omnis quaerat aut.","refresh_token":"Nostrum laborum minima."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"description":"Resource not found","example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PreconditionFailedError":{"title":"PreconditionFailedError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"description":"ETag mismatch","example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"title":"PreconditionRequiredError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"description":"If-Match header required","example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"title":"RedditAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ipsam temporibus sint et necessitatibus dolorem ut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fugit voluptas molestias architecto dicta quidem illo."},"label":{"type":"string","description":"Optional friendly name","example":"Quia aut sed fugiat adipisci eos rerum."},"project_id":{"type":"string","description":"Owning project","example":"Doloremque et qui perferendis."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1442766184420227830,"format":"int64"}},"example":{"account_id":"Voluptates neque expedita.","has_credentials":false,"id":"Qui sed fugiat.","label":"Sit iusto.","project_id":"Veniam debitis aut similique ullam temporibus qui.","status":"active","version":3889986261433875806},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"title":"RedditAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Quisquam ut eius."}},"example":{"account_id":"t2_gv9wtbfa","label":"Ipsum nihil cumque repellendus sint soluta."},"required":["account_id"]},"RedditAdsCredentials":{"title":"RedditAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Sed quibusdam reiciendis qui vel."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Amet et eaque."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Consequatur iste sed illo adipisci sunt."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Soluta consectetur porro quo quia incidunt.","client_secret":"Aperiam et ipsam vitae.","refresh_token":"Velit est quis aliquid aliquam."},"required":["client_id","client_secret","refresh_token"]},"TwitterAdsConnection":{"title":"TwitterAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Vitae quae debitis sunt aperiam ex voluptatibus."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"In fuga ut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Voluptate nisi natus sint."},"label":{"type":"string","description":"Optional friendly name","example":"Ut dolor velit."},"project_id":{"type":"string","description":"Owning project","example":"Beatae aliquam quia officiis hic ea."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6056629402053782931,"format":"int64"}},"example":{"account_id":"Non possimus quos voluptatem doloremque consequatur deleniti.","funding_instrument_id":"Fuga illo dolorem sit ex commodi sapiente.","has_credentials":false,"id":"Iusto quidem laudantium.","label":"Quaerat ut neque expedita nostrum.","project_id":"Ratione consequuntur aut et corrupti.","status":"error","version":5145245548849296683},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"title":"TwitterAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Excepturi dolorem autem ipsa autem provident."},"label":{"type":"string","description":"Optional friendly name","example":"Illum tenetur numquam."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Quis et odio tenetur optio.","label":"Iure quo culpa."},"required":["account_id"]},"TwitterAdsCredentials":{"title":"TwitterAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Veritatis excepturi veniam maxime incidunt asperiores voluptatem."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Nostrum dolorum iure eaque animi."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Ut sint."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Iste eveniet laudantium rerum."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Error necessitatibus et autem doloribus est.","access_token_secret":"Maxime labore illo maxime provident.","consumer_key":"Error laudantium.","consumer_secret":"Similique odio adipisci dolore iste."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"JWT issued by Heimdall; audience is this service.","name":"Authorization","in":"header"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/projects/{project_id}/briefs":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-brief lfx-v2-campaign-service-briefs","description":"Create a brief.","operationId":"lfx-v2-campaign-service-briefs#create-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-BriefRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsCreateBriefRequestBody","required":["brief"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-brief lfx-v2-campaign-service-briefs","description":"Get a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-brief lfx-v2-campaign-service-briefs","description":"Replace a brief (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-BriefRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsUpdateBriefRequestBody","required":["brief"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"delete-brief lfx-v2-campaign-service-briefs","description":"Archive a brief (soft delete).","operationId":"lfx-v2-campaign-service-briefs#delete-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/approve":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"approve-brief lfx-v2-campaign-service-briefs","description":"Approve a brief for campaign creation.","operationId":"lfx-v2-campaign-service-briefs#approve-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Brief","required":["id","project_id","program_type","event_slug","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-campaigns lfx-v2-campaign-service-briefs","description":"Create campaigns across the selected platforms (async -\u003e job).","operationId":"lfx-v2-campaign-service-briefs#create-campaigns","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-CampaignsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsCreateCampaignsRequestBody","required":["input"]}}],"responses":{"202":{"description":"Accepted response.","schema":{"$ref":"#/definitions/JobCreateResponse","required":["job_id","status","platforms"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-campaign lfx-v2-campaign-service-briefs","description":"Get one campaign under a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Campaign","required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-campaign lfx-v2-campaign-service-briefs","description":"Replace a campaign (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"type":"string","format":"uuid"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-CampaignRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceBriefsUpdateCampaignRequestBody","required":["campaign"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Campaign","required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GoogleAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Google-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/HubspotConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-HubspotRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/LinkedinAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Linkedin-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MetaAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Meta-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/MicrosoftAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Microsoft-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/RedditAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Reddit-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads":{"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","required":false,"type":"string"},{"name":"Update-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","required":["config"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"412":{"description":"Precondition Failed response.","schema":{"$ref":"#/definitions/PreconditionFailedError","required":["code","message"]}},"428":{"description":"Precondition Required response.","schema":{"$ref":"#/definitions/PreconditionRequiredError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","required":["config","credentials"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/TwitterAdsConnection","required":["id","project_id","account_id","has_credentials","status","version"]},"headers":{"ETag":{"description":"ETag header value (mirrors version)","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Set-Credential-Twitter-AdsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","required":["credentials"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ConnectionTestResult","required":["ok"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}},"/projects/{project_id}/jobs/{job_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-job lfx-v2-campaign-service-briefs","description":"Poll campaign-creation job status.","operationId":"lfx-v2-campaign-service-briefs#get-job","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"type":"string"},{"name":"job_id","in":"path","description":"Job UUID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/JobPollResponse","required":["job_id","status"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["code","message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["code","message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["code","message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["code","message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ConnServiceUnavailableError","required":["code","message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"Brief":{"title":"Brief","type":"object","properties":{"copy":{"description":"Ad copy","example":"Reprehenderit provident repudiandae sit."},"event_details":{"description":"Extracted event/course details","example":"Consequatur dolore dolor corporis consequatur voluptatem voluptates."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Et aspernatur."},"id":{"type":"string","description":"Brief UUID","example":"Ut modi voluptatum."},"keywords":{"description":"Keyword list","example":"Aut aut eos magnam quisquam et."},"platforms":{"type":"array","items":{"type":"string","example":"Sint voluptatibus quam dicta voluptate consequuntur."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Qui temporibus quis iste eaque.","Debitis nam.","Delectus ex aut quia."]},"program_type":{"type":"string","description":"Funnel context","example":"events","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Molestiae quibusdam consequuntur."},"status":{"type":"string","description":"Lifecycle status","example":"approved","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Ut sunt aliquam."},"url":{"type":"string","description":"Event/course page URL","example":"Nobis soluta odit odio praesentium perferendis."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":516504771697389432,"format":"int64"}},"example":{"copy":"Facilis qui libero.","event_details":"Et itaque rem eos et deleniti dolores.","event_slug":"Fugiat aut dicta libero.","id":"Quos est minus officiis beatae sed aut.","keywords":"Facere qui omnis.","platforms":["Laudantium error eum et aut.","Beatae harum similique corporis id eum."],"program_type":"membership","project_id":"Nihil est quia qui fugiat est.","status":"archived","targeting":"Enim vitae officia dolorem odio.","url":"Culpa est possimus dolores dignissimos.","version":667273962263484824},"required":["id","project_id","program_type","event_slug","status","version"]},"BriefInput":{"title":"BriefInput","type":"object","properties":{"copy":{"description":"Ad copy","example":"Exercitationem accusantium nihil."},"event_details":{"description":"Extracted event/course details","example":"Dolores sit."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Excepturi minima illo dolores."},"keywords":{"description":"Keyword list","example":"Quod alias ipsum voluptates pariatur magni."},"platforms":{"type":"array","items":{"type":"string","example":"Iure quis."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Mollitia nihil ducimus.","Non quo necessitatibus.","Molestiae est accusantium laudantium nostrum."]},"program_type":{"type":"string","description":"Funnel context","example":"education","enum":["events","education","membership"]},"targeting":{"description":"Targeting recommendation","example":"Amet omnis."},"url":{"type":"string","description":"Event/course page URL","example":"A sint quod sunt quae et."}},"example":{"copy":"Earum minus autem omnis qui debitis eum.","event_details":"Cupiditate mollitia hic vel ut.","event_slug":"Fugit dolor et natus sit cupiditate non.","keywords":"Sed veniam dolores quidem necessitatibus voluptatem.","platforms":["Esse officia explicabo dolor deserunt aspernatur.","Aspernatur illo accusamus."],"program_type":"education","targeting":"Sit reiciendis et vel beatae molestiae.","url":"Modi possimus consequatur."},"required":["program_type","event_slug"]},"Campaign":{"title":"Campaign","type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Unde hic est quia pariatur odit ullam."},"campaign_name":{"type":"string","description":"Campaign name","example":"In id voluptatem nihil quaerat."},"id":{"type":"string","description":"Campaign UUID","example":"Accusamus in quod."},"platform":{"type":"string","description":"Channel","example":"Ex id corrupti sed quo perferendis."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Ut quisquam nisi repellendus quos magnam."},"project_id":{"type":"string","description":"Owning project","example":"Accusamus incidunt minus veritatis."},"status":{"type":"string","description":"Campaign status","example":"Pariatur repellat vel."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6425243200048993167,"format":"int64"}},"example":{"brief_id":"Distinctio magnam.","campaign_name":"Sint est sed.","id":"Rerum accusamus velit error perferendis quae qui.","platform":"Architecto deserunt vero ducimus distinctio.","platform_campaign_id":"Quia magni aut nisi est.","project_id":"Dolorem deserunt quasi in aut nam rerum.","status":"Voluptas dolorem necessitatibus illum.","version":2836554557236834455},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"CampaignCreateInput":{"title":"CampaignCreateInput","type":"object","properties":{"config":{"description":"Per-platform campaign configuration","example":"Repudiandae aut reiciendis recusandae."},"platforms":{"type":"array","items":{"type":"string","example":"reddit-ads","enum":["google-ads","linkedin-ads","meta-ads","reddit-ads","twitter-ads","microsoft-ads","hubspot"]},"description":"Platforms to create campaigns on (binding selection)","example":["reddit-ads"],"minItems":1}},"example":{"config":"Aut non.","platforms":["reddit-ads","linkedin-ads","google-ads"]},"required":["platforms"]},"CampaignUpdateInput":{"title":"CampaignUpdateInput","type":"object","properties":{"campaign_name":{"type":"string","description":"Campaign name","example":"Ipsa nihil esse."},"config":{"description":"Campaign configuration snapshot","example":"Et impedit quod."},"status":{"type":"string","description":"Campaign status","example":"In consectetur et culpa quaerat vel sed."}},"example":{"campaign_name":"Magni omnis.","config":"Sit id incidunt.","status":"Facere eum aliquid est repellat assumenda."},"required":["campaign_name","status"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"description":"Conflict","example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"title":"ConnServiceUnavailableError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"title":"ConnectionTestResult","type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Dolorem laborum."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":false}},"example":{"message":"Ea ducimus ipsum dolorem dignissimos.","ok":false},"required":["ok"]},"GoogleAdsConnection":{"title":"GoogleAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Id ea."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Illum nesciunt."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptas omnis quidem dolor ipsum voluptas sed."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Consectetur occaecati earum unde ut sint."},"project_id":{"type":"string","description":"Owning project","example":"Odit corrupti laudantium consequatur hic."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2949193160538820166,"format":"int64"}},"example":{"account_id":"Totam cum veniam.","has_credentials":true,"id":"Eum expedita non voluptas sit.","label":"Hic quibusdam eveniet nostrum doloribus voluptas mollitia.","login_customer_id":"Optio quia doloremque commodi rerum esse.","project_id":"Ad ab quasi tempora dolor quis.","status":"active","version":3605710116333746042},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"title":"GoogleAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"title":"GoogleAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Laudantium quia."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Maxime recusandae."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Accusamus debitis explicabo praesentium."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Omnis totam eos non."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Molestias ducimus id.","client_secret":"Eius aut quo et.","developer_token":"Ex et alias ea asperiores quas.","refresh_token":"Laudantium error sit nihil iure officiis nemo."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"title":"HubspotConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Hic magni."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Et nihil facilis sunt praesentium."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Tenetur cumque rerum."},"label":{"type":"string","description":"Optional friendly name","example":"Fugit dolorum nulla quo veritatis id."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Omnis aut voluptatem voluptas nisi eum."},"project_id":{"type":"string","description":"Owning project","example":"Modi esse voluptatibus."},"sender_email":{"type":"string","description":"Default sender address","example":"Est blanditiis maxime et ea."},"sender_name":{"type":"string","description":"Default sender name","example":"Eum inventore quisquam praesentium nihil."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4901984733277471780,"format":"int64"}},"example":{"account_id":"Eligendi incidunt accusamus excepturi.","brand_kit":"Veritatis voluptatem aut architecto aliquam.","has_credentials":false,"id":"Ad ex.","label":"Atque reprehenderit.","portal_id":"Voluptas quod minima.","project_id":"Dolore delectus ut beatae eaque.","sender_email":"Qui molestiae id aut debitis iure enim.","sender_name":"Est dolorem quod enim pariatur et.","status":"inactive","version":3687898389557176015},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"title":"HubspotConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Ex doloribus quia mollitia."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Cum et ab ipsum."},"label":{"type":"string","description":"Optional friendly name","example":"Labore sapiente dolores tenetur non accusamus."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Ex et amet sunt."},"sender_email":{"type":"string","description":"Default sender address","example":"Ad quae nisi."},"sender_name":{"type":"string","description":"Default sender name","example":"Reiciendis enim."}},"example":{"account_id":"Tempora qui eum hic vel aspernatur at.","brand_kit":"Et sed praesentium libero.","label":"Qui nemo nihil dolorum consequatur.","portal_id":"Est ea sed sit.","sender_email":"Incidunt voluptas non necessitatibus.","sender_name":"Ad quis aperiam deserunt nostrum."},"required":["account_id"]},"HubspotCredentials":{"title":"HubspotCredentials","type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Qui minima in nulla sunt autem eos."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Provident dicta tenetur voluptatem voluptate dolor."},"required":["private_app_token"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"JobCreateResponse":{"title":"JobCreateResponse","type":"object","properties":{"job_id":{"type":"string","description":"Poll GET /projects/{projectId}/jobs/{jobId}","example":"Natus vitae consequatur eos sit aut ut."},"platforms":{"type":"array","items":{"type":"string","example":"Corrupti sit eos labore."},"description":"Platforms this job will create on","example":["Consectetur tenetur eum.","Aut ut sit nisi architecto.","Rerum et recusandae possimus quis."]},"status":{"type":"string","description":"Initial status (always 'queued' on create)","example":"queued","enum":["queued"]}},"example":{"job_id":"Praesentium voluptate.","platforms":["Temporibus eveniet eos omnis cumque nemo ipsam.","Sed natus."],"status":"queued"},"required":["job_id","status","platforms"]},"JobPollResponse":{"title":"JobPollResponse","type":"object","properties":{"error":{"type":"string","description":"Terminal error, if any","example":"Aliquid aspernatur."},"job_id":{"type":"string","description":"Job UUID","example":"Non sed hic minima est distinctio."},"result":{"type":"array","items":{"$ref":"#/definitions/PlatformResult"},"description":"Per-platform results, written once when the job reaches a terminal state","example":[{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."}]},"status":{"type":"string","description":"Job status","example":"running","enum":["queued","running","succeeded","partial","failed"]}},"example":{"error":"Non asperiores quidem natus est nostrum.","job_id":"In cupiditate consequatur et dolorem quod.","result":[{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."}],"status":"queued"},"required":["job_id","status"]},"LfxV2CampaignServiceBriefsCreateBriefRequestBody":{"title":"LfxV2CampaignServiceBriefsCreateBriefRequestBody","type":"object","properties":{"brief":{"$ref":"#/definitions/BriefInput"}},"example":{"brief":{"copy":"Autem veniam.","event_details":"Vel autem voluptates.","event_slug":"Et est laboriosam.","keywords":"Enim unde omnis.","platforms":["Nam exercitationem consectetur sint.","Necessitatibus ex ea ullam corrupti.","Et omnis accusantium.","Iure modi asperiores ut."],"program_type":"education","targeting":"Quam sed rem quia consequuntur ut.","url":"Facere sint totam sapiente vitae numquam ut."}},"required":["brief"]},"LfxV2CampaignServiceBriefsCreateCampaignsRequestBody":{"title":"LfxV2CampaignServiceBriefsCreateCampaignsRequestBody","type":"object","properties":{"input":{"$ref":"#/definitions/CampaignCreateInput"}},"example":{"input":{"config":"Quia quos ut tempora consequuntur cumque repudiandae.","platforms":["microsoft-ads","microsoft-ads","linkedin-ads"]}},"required":["input"]},"LfxV2CampaignServiceBriefsUpdateBriefRequestBody":{"title":"LfxV2CampaignServiceBriefsUpdateBriefRequestBody","type":"object","properties":{"brief":{"$ref":"#/definitions/BriefInput"}},"example":{"brief":{"copy":"Autem veniam.","event_details":"Vel autem voluptates.","event_slug":"Et est laboriosam.","keywords":"Enim unde omnis.","platforms":["Nam exercitationem consectetur sint.","Necessitatibus ex ea ullam corrupti.","Et omnis accusantium.","Iure modi asperiores ut."],"program_type":"education","targeting":"Quam sed rem quia consequuntur ut.","url":"Facere sint totam sapiente vitae numquam ut."}},"required":["brief"]},"LfxV2CampaignServiceBriefsUpdateCampaignRequestBody":{"title":"LfxV2CampaignServiceBriefsUpdateCampaignRequestBody","type":"object","properties":{"campaign":{"$ref":"#/definitions/CampaignUpdateInput"}},"example":{"campaign":{"campaign_name":"Eos doloribus autem corrupti sed voluptatum laborum.","config":"Perferendis qui maiores aut reiciendis.","status":"Suscipit quas sunt eos nulla."}},"required":["campaign"]},"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"},"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."},"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"},"credentials":{"access_token":"Illo minima eaque earum."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."},"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."},"credentials":{"client_id":"Officiis aut fugiat optio ut.","client_secret":"Et voluptas tenetur nobis facere.","developer_token":"Blanditiis mollitia dolores illo nostrum magnam.","refresh_token":"Quo corporis recusandae dicta quibusdam."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."},"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsCreateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."},"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}},"required":["config","credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialGoogleAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Illo minima eaque earum."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/MicrosoftAdsCredentials"}},"example":{"credentials":{"client_id":"Officiis aut fugiat optio ut.","client_secret":"Et voluptas tenetur nobis facere.","developer_token":"Blanditiis mollitia dolores illo nostrum magnam.","refresh_token":"Quo corporis recusandae dicta quibusdam."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody","type":"object","properties":{"credentials":{"$ref":"#/definitions/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}},"required":["credentials"]},"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateHubspotRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMetaAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."}},"required":["config"]},"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody":{"title":"LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody","type":"object","properties":{"config":{"$ref":"#/definitions/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."}},"required":["config"]},"LinkedinAdsConnection":{"title":"LinkedinAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quo vel iste aut cum voluptatem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quo ullam ad iusto quos."},"label":{"type":"string","description":"Optional friendly name","example":"Tenetur ut."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Facere voluptas quis nihil."},"project_id":{"type":"string","description":"Owning project","example":"Est placeat explicabo aliquid."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1833221466395792722,"format":"int64"}},"example":{"account_id":"Vero ut et neque soluta distinctio voluptas.","has_credentials":false,"id":"Rem asperiores eligendi vero.","label":"Facere quas qui aut rerum.","org_id":"Fugit eum.","project_id":"Voluptatem fugiat et.","status":"active","version":5717670313645740058},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"title":"LinkedinAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Necessitatibus voluptatem incidunt et totam esse rerum."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Dolorem delectus.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"title":"LinkedinAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Voluptatem incidunt cum."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Occaecati quaerat eum quia a excepturi."},"required":["access_token"]},"MetaAdsConnection":{"title":"MetaAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ad dolorem et."},"app_id":{"type":"string","description":"Meta app ID","example":"Impedit unde voluptatem quia ab recusandae praesentium."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ut possimus quidem eos culpa quaerat."},"label":{"type":"string","description":"Optional friendly name","example":"Inventore ratione id vel iusto optio repellendus."},"page_id":{"type":"string","description":"Facebook page ID","example":"Sunt aut."},"project_id":{"type":"string","description":"Owning project","example":"Inventore aut molestiae voluptas hic."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6328500858220715511,"format":"int64"}},"example":{"account_id":"Iusto fugiat ad et.","app_id":"Ea eum.","has_credentials":true,"id":"Est magni odio aut omnis vel.","label":"Ullam consequatur ut eveniet tempore est.","page_id":"Mollitia vel.","project_id":"Iure ducimus voluptatem.","status":"error","version":6284855428062318470},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"title":"MetaAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Tempore rem quo fuga accusantium voluptas ratione."},"label":{"type":"string","description":"Optional friendly name","example":"Qui qui."},"page_id":{"type":"string","description":"Facebook page ID","example":"Repudiandae quae aut."}},"example":{"account_id":"act_193556282970417","app_id":"Perspiciatis quam iste possimus ipsam.","label":"Esse et labore earum.","page_id":"Numquam optio possimus velit quod."},"required":["account_id"]},"MetaAdsCredentials":{"title":"MetaAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Enim id."},"app_secret":{"type":"string","description":"Meta app secret","example":"Eveniet quia voluptatem impedit cumque doloribus."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Vel necessitatibus vel voluptates tenetur veniam repellat.","app_secret":"Minima vitae illo cumque optio impedit."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"title":"MicrosoftAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Repudiandae facilis blanditiis libero nam."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Quam quia possimus deserunt et quidem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Qui et assumenda quas."},"label":{"type":"string","description":"Optional friendly name","example":"Delectus blanditiis magnam sed iure amet."},"project_id":{"type":"string","description":"Owning project","example":"Quisquam animi."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5244135457294865936,"format":"int64"}},"example":{"account_id":"Officiis aliquid explicabo eaque ab.","customer_id":"Ea iusto eveniet velit sint odit sed.","has_credentials":true,"id":"Laudantium molestiae omnis doloremque consequuntur perspiciatis modi.","label":"Vero et atque in impedit velit omnis.","project_id":"Quae sed.","status":"inactive","version":187687687190489160},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"title":"MicrosoftAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Esse voluptate optio quis quae quam."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Odio quia omnis commodi."},"label":{"type":"string","description":"Optional friendly name","example":"Eos nemo cupiditate."}},"example":{"account_id":"Illo porro velit perferendis.","customer_id":"Ipsum aut voluptas molestias unde.","label":"Pariatur nostrum."},"required":["account_id"]},"MicrosoftAdsCredentials":{"title":"MicrosoftAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Voluptas ut vero autem consequuntur optio omnis."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Pariatur omnis ut velit dolorem dolor."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Nihil ut."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Omnis in qui."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Mollitia nulla.","client_secret":"Minus voluptatem ab delectus porro.","developer_token":"Accusamus quia velit commodi.","refresh_token":"Veniam molestias."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"description":"Resource not found","example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PlatformResult":{"title":"PlatformResult","type":"object","properties":{"campaign_id":{"type":"string","description":"Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost.","example":"Nostrum quia laboriosam ut nobis."},"error":{"type":"string","description":"Failure reason (present when not ok)","example":"Velit quis."},"ok":{"type":"boolean","description":"Whether the campaign was created (or reused) successfully","example":true},"platform":{"type":"string","description":"Platform this result is for","example":"Perferendis culpa voluptatem nostrum eaque deleniti."}},"example":{"campaign_id":"Architecto quae error.","error":"Placeat et minus et.","ok":true,"platform":"Aliquam animi."},"required":["platform","ok"]},"PreconditionFailedError":{"title":"PreconditionFailedError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"description":"ETag mismatch","example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"title":"PreconditionRequiredError","type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"description":"If-Match header required","example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"title":"RedditAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptate dolorum id ut molestias id."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aut repudiandae voluptatibus odit autem."},"label":{"type":"string","description":"Optional friendly name","example":"Et earum id."},"project_id":{"type":"string","description":"Owning project","example":"Dicta qui."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7470923779752943348,"format":"int64"}},"example":{"account_id":"Vitae nemo sint et consequatur vel quisquam.","has_credentials":true,"id":"Non assumenda molestiae.","label":"Autem aliquam voluptatibus assumenda mollitia molestias eos.","project_id":"Impedit vitae molestiae.","status":"error","version":3850958169697422368},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"title":"RedditAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Sint possimus."}},"example":{"account_id":"t2_gv9wtbfa","label":"Dolorem neque hic voluptatem ad similique sunt."},"required":["account_id"]},"RedditAdsCredentials":{"title":"RedditAdsCredentials","type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Doloremque dicta."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Ratione impedit recusandae dolor consequatur."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Vel temporibus sunt nulla esse sed."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Voluptatem beatae repellat veritatis perferendis veritatis.","client_secret":"Facere sed ea qui pariatur adipisci.","refresh_token":"Enim esse dolores."},"required":["client_id","client_secret","refresh_token"]},"TwitterAdsConnection":{"title":"TwitterAdsConnection","type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"In autem neque nihil laborum omnis."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Dignissimos consequatur reiciendis delectus enim quas."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Iste exercitationem voluptate quia esse nihil sed."},"label":{"type":"string","description":"Optional friendly name","example":"Neque maiores quo aliquam in rem."},"project_id":{"type":"string","description":"Owning project","example":"Est perferendis eos et cumque nulla."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3490326709734661888,"format":"int64"}},"example":{"account_id":"Rerum et.","funding_instrument_id":"Inventore quasi officiis aut omnis itaque.","has_credentials":true,"id":"Porro eius est porro eos.","label":"Eveniet vel aut.","project_id":"Quia nihil quo repudiandae laboriosam non.","status":"inactive","version":1231088733548867560},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"title":"TwitterAdsConnectionConfig","type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Provident velit voluptatibus voluptatem."},"label":{"type":"string","description":"Optional friendly name","example":"Vero voluptatibus quis."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Dolores vero.","label":"Dolorem qui mollitia sequi non."},"required":["account_id"]},"TwitterAdsCredentials":{"title":"TwitterAdsCredentials","type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Et voluptatem aut est voluptatibus placeat."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Pariatur perferendis explicabo expedita et velit accusamus."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Aut quibusdam repellendus qui numquam explicabo."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Quaerat minima a voluptatem."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Recusandae totam soluta.","access_token_secret":"Praesentium libero.","consumer_key":"Deleniti placeat rerum.","consumer_secret":"Nulla eos."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"JWT issued by Heimdall; audience is this service.","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index b20cb445..289de59c 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -13,6 +13,708 @@ produces: - application/xml - application/gob paths: + /projects/{project_id}/briefs: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-brief lfx-v2-campaign-service-briefs + description: Create a brief. + operationId: lfx-v2-campaign-service-briefs#create-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: Create-BriefRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsCreateBriefRequestBody' + required: + - brief + responses: + "201": + description: Created response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-brief lfx-v2-campaign-service-briefs + description: Get a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-brief lfx-v2-campaign-service-briefs + description: Replace a brief (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + - name: Update-BriefRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsUpdateBriefRequestBody' + required: + - brief + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + delete: + tags: + - lfx-v2-campaign-service-briefs + summary: delete-brief lfx-v2-campaign-service-briefs + description: Archive a brief (soft delete). + operationId: lfx-v2-campaign-service-briefs#delete-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "204": + description: No Content response. + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/approve: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: approve-brief lfx-v2-campaign-service-briefs + description: Approve a brief for campaign creation. + operationId: lfx-v2-campaign-service-briefs#approve-brief + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Brief' + required: + - id + - project_id + - program_type + - event_slug + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns: + post: + tags: + - lfx-v2-campaign-service-briefs + summary: create-campaigns lfx-v2-campaign-service-briefs + description: Create campaigns across the selected platforms (async -> job). + operationId: lfx-v2-campaign-service-briefs#create-campaigns + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: Create-CampaignsRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsCreateCampaignsRequestBody' + required: + - input + responses: + "202": + description: Accepted response. + schema: + $ref: '#/definitions/JobCreateResponse' + required: + - job_id + - status + - platforms + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-campaign lfx-v2-campaign-service-briefs + description: Get one campaign under a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: campaign_id + in: path + description: Campaign UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Campaign' + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-campaign lfx-v2-campaign-service-briefs + description: Replace a campaign (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: brief_id + in: path + description: Brief UUID + required: true + type: string + format: uuid + - name: campaign_id + in: path + description: Campaign UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + required: false + type: string + - name: Update-CampaignRequestBody + in: body + required: true + schema: + $ref: '#/definitions/LfxV2CampaignServiceBriefsUpdateCampaignRequestBody' + required: + - campaign + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/Campaign' + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + headers: + ETag: + description: ETag header value (mirrors version) + type: string + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "412": + description: Precondition Failed response. + schema: + $ref: '#/definitions/PreconditionFailedError' + required: + - code + - message + "428": + description: Precondition Required response. + schema: + $ref: '#/definitions/PreconditionRequiredError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] /projects/{project_id}/connection-google-ads: get: tags: @@ -2659,6 +3361,77 @@ paths: - http security: - jwt_header_Authorization: [] + /projects/{project_id}/jobs/{job_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-job lfx-v2-campaign-service-briefs + description: Poll campaign-creation job status. + operationId: lfx-v2-campaign-service-briefs#get-job + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + type: string + - name: job_id + in: path + description: Job UUID + required: true + type: string + format: uuid + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/JobPollResponse' + required: + - job_id + - status + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/BadRequestError' + required: + - code + - message + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - code + - message + "409": + description: Conflict response. + schema: + $ref: '#/definitions/ConflictError' + required: + - code + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - code + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ConnServiceUnavailableError' + required: + - code + - message + schemes: + - http + security: + - jwt_header_Authorization: [] definitions: BadRequestError: title: BadRequestError @@ -2679,6 +3452,255 @@ definitions: required: - code - message + Brief: + title: Brief + type: object + properties: + copy: + description: Ad copy + example: Reprehenderit provident repudiandae sit. + event_details: + description: Extracted event/course details + example: Consequatur dolore dolor corporis consequatur voluptatem voluptates. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Et aspernatur. + id: + type: string + description: Brief UUID + example: Ut modi voluptatum. + keywords: + description: Keyword list + example: Aut aut eos magnam quisquam et. + platforms: + type: array + items: + type: string + example: Sint voluptatibus quam dicta voluptate consequuntur. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Qui temporibus quis iste eaque. + - Debitis nam. + - Delectus ex aut quia. + program_type: + type: string + description: Funnel context + example: events + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Molestiae quibusdam consequuntur. + status: + type: string + description: Lifecycle status + example: approved + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Ut sunt aliquam. + url: + type: string + description: Event/course page URL + example: Nobis soluta odit odio praesentium perferendis. + version: + type: integer + description: Optimistic-concurrency version + example: 516504771697389432 + format: int64 + example: + copy: Facilis qui libero. + event_details: Et itaque rem eos et deleniti dolores. + event_slug: Fugiat aut dicta libero. + id: Quos est minus officiis beatae sed aut. + keywords: Facere qui omnis. + platforms: + - Laudantium error eum et aut. + - Beatae harum similique corporis id eum. + program_type: membership + project_id: Nihil est quia qui fugiat est. + status: archived + targeting: Enim vitae officia dolorem odio. + url: Culpa est possimus dolores dignissimos. + version: 667273962263484824 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + BriefInput: + title: BriefInput + type: object + properties: + copy: + description: Ad copy + example: Exercitationem accusantium nihil. + event_details: + description: Extracted event/course details + example: Dolores sit. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Excepturi minima illo dolores. + keywords: + description: Keyword list + example: Quod alias ipsum voluptates pariatur magni. + platforms: + type: array + items: + type: string + example: Iure quis. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Mollitia nihil ducimus. + - Non quo necessitatibus. + - Molestiae est accusantium laudantium nostrum. + program_type: + type: string + description: Funnel context + example: education + enum: + - events + - education + - membership + targeting: + description: Targeting recommendation + example: Amet omnis. + url: + type: string + description: Event/course page URL + example: A sint quod sunt quae et. + example: + copy: Earum minus autem omnis qui debitis eum. + event_details: Cupiditate mollitia hic vel ut. + event_slug: Fugit dolor et natus sit cupiditate non. + keywords: Sed veniam dolores quidem necessitatibus voluptatem. + platforms: + - Esse officia explicabo dolor deserunt aspernatur. + - Aspernatur illo accusamus. + program_type: education + targeting: Sit reiciendis et vel beatae molestiae. + url: Modi possimus consequatur. + required: + - program_type + - event_slug + Campaign: + title: Campaign + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Unde hic est quia pariatur odit ullam. + campaign_name: + type: string + description: Campaign name + example: In id voluptatem nihil quaerat. + id: + type: string + description: Campaign UUID + example: Accusamus in quod. + platform: + type: string + description: Channel + example: Ex id corrupti sed quo perferendis. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Ut quisquam nisi repellendus quos magnam. + project_id: + type: string + description: Owning project + example: Accusamus incidunt minus veritatis. + status: + type: string + description: Campaign status + example: Pariatur repellat vel. + version: + type: integer + description: Optimistic-concurrency version + example: 6425243200048993167 + format: int64 + example: + brief_id: Distinctio magnam. + campaign_name: Sint est sed. + id: Rerum accusamus velit error perferendis quae qui. + platform: Architecto deserunt vero ducimus distinctio. + platform_campaign_id: Quia magni aut nisi est. + project_id: Dolorem deserunt quasi in aut nam rerum. + status: Voluptas dolorem necessitatibus illum. + version: 2836554557236834455 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + CampaignCreateInput: + title: CampaignCreateInput + type: object + properties: + config: + description: Per-platform campaign configuration + example: Repudiandae aut reiciendis recusandae. + platforms: + type: array + items: + type: string + example: reddit-ads + enum: + - google-ads + - linkedin-ads + - meta-ads + - reddit-ads + - twitter-ads + - microsoft-ads + - hubspot + description: Platforms to create campaigns on (binding selection) + example: + - reddit-ads + minItems: 1 + example: + config: Aut non. + platforms: + - reddit-ads + - linkedin-ads + - google-ads + required: + - platforms + CampaignUpdateInput: + title: CampaignUpdateInput + type: object + properties: + campaign_name: + type: string + description: Campaign name + example: Ipsa nihil esse. + config: + description: Campaign configuration snapshot + example: Et impedit quod. + status: + type: string + description: Campaign status + example: In consectetur et culpa quaerat vel sed. + example: + campaign_name: Magni omnis. + config: Sit id incidunt. + status: Facere eum aliquid est repellat assumenda. + required: + - campaign_name + - status ConflictError: title: ConflictError type: object @@ -2691,7 +3713,7 @@ definitions: type: string description: Error message example: A connection for this provider already exists on the project. - description: A connection already exists for this provider on the project + description: Conflict example: code: "409" message: A connection for this provider already exists on the project. @@ -2724,14 +3746,14 @@ definitions: message: type: string description: Human-readable detail - example: Labore aut eum porro. + example: Dolorem laborum. ok: type: boolean description: Whether the credential authenticated against the provider - example: true + example: false example: - message: Necessitatibus consequatur natus tempora ut. - ok: true + message: Ea ducimus ipsum dolorem dignissimos. + ok: false required: - ok GoogleAdsConnection: @@ -2741,27 +3763,27 @@ definitions: account_id: type: string description: Provider account identifier - example: Aut voluptatem inventore ab. + example: Id ea. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Quis et maxime ipsam voluptatem repudiandae laudantium. + example: Illum nesciunt. label: type: string description: Optional friendly name - example: Nobis ipsam. + example: Voluptas omnis quidem dolor ipsum voluptas sed. login_customer_id: type: string description: Manager account used for API access - example: Iusto quis magni et velit. + example: Consectetur occaecati earum unde ut sint. project_id: type: string description: Owning project - example: Ut ipsum id. + example: Odit corrupti laudantium consequatur hic. status: type: string description: Connection status @@ -2774,17 +3796,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 8470973010316083422 + example: 2949193160538820166 format: int64 example: - account_id: Aut id molestiae sit. + account_id: Totam cum veniam. has_credentials: true - id: Magni voluptas odio assumenda totam. - label: Nam cumque. - login_customer_id: Qui esse. - project_id: Est praesentium dolor officiis deleniti. + id: Eum expedita non voluptas sit. + label: Hic quibusdam eveniet nostrum doloribus voluptas mollitia. + login_customer_id: Optio quia doloremque commodi rerum esse. + project_id: Ad ab quasi tempora dolor quis. status: active - version: 7862773009082385007 + version: 3605710116333746042 required: - id - project_id @@ -2821,25 +3843,25 @@ definitions: client_id: type: string description: OAuth client id - example: Ducimus sequi. + example: Laudantium quia. client_secret: type: string description: OAuth client secret - example: Autem ab voluptas culpa. + example: Maxime recusandae. developer_token: type: string description: Google Ads developer token - example: Quia tenetur ducimus. + example: Accusamus debitis explicabo praesentium. refresh_token: type: string description: OAuth refresh token - example: Aut et. + example: Omnis totam eos non. description: Google Ads OAuth credential set. Write-only; never returned. example: - client_id: Alias non sequi consequatur dolorum. - client_secret: Voluptatem voluptas omnis est tempora ipsum aut. - developer_token: Harum et minus voluptas. - refresh_token: Facere harum enim consequatur id voluptatem. + client_id: Molestias ducimus id. + client_secret: Eius aut quo et. + developer_token: Ex et alias ea asperiores quas. + refresh_token: Laudantium error sit nihil iure officiis nemo. required: - refresh_token - client_id @@ -2852,11 +3874,11 @@ definitions: account_id: type: string description: Provider account identifier - example: Sed maiores qui sed exercitationem quam. + example: Hic magni. brand_kit: type: string description: Per-project brand kit selector - example: Asperiores facere et exercitationem soluta possimus. + example: Et nihil facilis sunt praesentium. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -2864,31 +3886,31 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: In expedita. + example: Tenetur cumque rerum. label: type: string description: Optional friendly name - example: Suscipit sed minus soluta quasi. + example: Fugit dolorum nulla quo veritatis id. portal_id: type: string description: HubSpot portal/account ID - example: Quia ducimus. + example: Omnis aut voluptatem voluptas nisi eum. project_id: type: string description: Owning project - example: Magnam cumque quasi magni ducimus. + example: Modi esse voluptatibus. sender_email: type: string description: Default sender address - example: Et veritatis voluptas nobis. + example: Est blanditiis maxime et ea. sender_name: type: string description: Default sender name - example: Doloremque architecto ea et. + example: Eum inventore quisquam praesentium nihil. status: type: string description: Connection status - example: inactive + example: error enum: - active - inactive @@ -2897,20 +3919,20 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 4054743500330389510 + example: 4901984733277471780 format: int64 example: - account_id: Est iste. - brand_kit: Quae aut culpa impedit sit. - has_credentials: true - id: Aliquid et in. - label: Distinctio aut. - portal_id: Earum modi esse. - project_id: Consequuntur omnis a dignissimos voluptate. - sender_email: Possimus placeat enim ab. - sender_name: Esse et qui et voluptatibus tempore. - status: active - version: 3449749392517333935 + account_id: Eligendi incidunt accusamus excepturi. + brand_kit: Veritatis voluptatem aut architecto aliquam. + has_credentials: false + id: Ad ex. + label: Atque reprehenderit. + portal_id: Voluptas quod minima. + project_id: Dolore delectus ut beatae eaque. + sender_email: Qui molestiae id aut debitis iure enim. + sender_name: Est dolorem quod enim pariatur et. + status: inactive + version: 3687898389557176015 required: - id - project_id @@ -2925,34 +3947,34 @@ definitions: account_id: type: string description: HubSpot list/audience ID - example: Velit labore non. + example: Ex doloribus quia mollitia. brand_kit: type: string description: Per-project brand kit selector - example: Dolorem sapiente. + example: Cum et ab ipsum. label: type: string description: Optional friendly name - example: Nulla adipisci aperiam dolorem molestias. + example: Labore sapiente dolores tenetur non accusamus. portal_id: type: string description: HubSpot portal/account ID - example: Odit est esse. + example: Ex et amet sunt. sender_email: type: string description: Default sender address - example: Ea cum et magni adipisci magnam. + example: Ad quae nisi. sender_name: type: string description: Default sender name - example: Atque labore qui odio et corporis. + example: Reiciendis enim. example: - account_id: Sint autem. - brand_kit: Debitis soluta sed quod dicta aperiam eveniet. - label: Aut natus. - portal_id: Deleniti itaque voluptas magnam. - sender_email: Voluptatem modi velit vitae optio ut. - sender_name: Consequuntur sit libero autem ut. + account_id: Tempora qui eum hic vel aspernatur at. + brand_kit: Et sed praesentium libero. + label: Qui nemo nihil dolorum consequatur. + portal_id: Est ea sed sit. + sender_email: Incidunt voluptas non necessitatibus. + sender_name: Ad quis aperiam deserunt nostrum. required: - account_id HubspotCredentials: @@ -2962,10 +3984,10 @@ definitions: private_app_token: type: string description: HubSpot private app token - example: Dolor id. + example: Qui minima in nulla sunt autem eos. description: HubSpot private app token. Write-only; never returned. example: - private_app_token: Accusantium sequi modi corrupti esse mollitia. + private_app_token: Provident dicta tenetur voluptatem voluptate dolor. required: - private_app_token InternalServerError: @@ -2987,6 +4009,172 @@ definitions: required: - code - message + JobCreateResponse: + title: JobCreateResponse + type: object + properties: + job_id: + type: string + description: Poll GET /projects/{projectId}/jobs/{jobId} + example: Natus vitae consequatur eos sit aut ut. + platforms: + type: array + items: + type: string + example: Corrupti sit eos labore. + description: Platforms this job will create on + example: + - Consectetur tenetur eum. + - Aut ut sit nisi architecto. + - Rerum et recusandae possimus quis. + status: + type: string + description: Initial status (always 'queued' on create) + example: queued + enum: + - queued + example: + job_id: Praesentium voluptate. + platforms: + - Temporibus eveniet eos omnis cumque nemo ipsam. + - Sed natus. + status: queued + required: + - job_id + - status + - platforms + JobPollResponse: + title: JobPollResponse + type: object + properties: + error: + type: string + description: Terminal error, if any + example: Aliquid aspernatur. + job_id: + type: string + description: Job UUID + example: Non sed hic minima est distinctio. + result: + type: array + items: + $ref: '#/definitions/PlatformResult' + description: Per-platform results, written once when the job reaches a terminal state + example: + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + status: + type: string + description: Job status + example: running + enum: + - queued + - running + - succeeded + - partial + - failed + example: + error: Non asperiores quidem natus est nostrum. + job_id: In cupiditate consequatur et dolorem quod. + result: + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + status: queued + required: + - job_id + - status + LfxV2CampaignServiceBriefsCreateBriefRequestBody: + title: LfxV2CampaignServiceBriefsCreateBriefRequestBody + type: object + properties: + brief: + $ref: '#/definitions/BriefInput' + example: + brief: + copy: Autem veniam. + event_details: Vel autem voluptates. + event_slug: Et est laboriosam. + keywords: Enim unde omnis. + platforms: + - Nam exercitationem consectetur sint. + - Necessitatibus ex ea ullam corrupti. + - Et omnis accusantium. + - Iure modi asperiores ut. + program_type: education + targeting: Quam sed rem quia consequuntur ut. + url: Facere sint totam sapiente vitae numquam ut. + required: + - brief + LfxV2CampaignServiceBriefsCreateCampaignsRequestBody: + title: LfxV2CampaignServiceBriefsCreateCampaignsRequestBody + type: object + properties: + input: + $ref: '#/definitions/CampaignCreateInput' + example: + input: + config: Quia quos ut tempora consequuntur cumque repudiandae. + platforms: + - microsoft-ads + - microsoft-ads + - linkedin-ads + required: + - input + LfxV2CampaignServiceBriefsUpdateBriefRequestBody: + title: LfxV2CampaignServiceBriefsUpdateBriefRequestBody + type: object + properties: + brief: + $ref: '#/definitions/BriefInput' + example: + brief: + copy: Autem veniam. + event_details: Vel autem voluptates. + event_slug: Et est laboriosam. + keywords: Enim unde omnis. + platforms: + - Nam exercitationem consectetur sint. + - Necessitatibus ex ea ullam corrupti. + - Et omnis accusantium. + - Iure modi asperiores ut. + program_type: education + targeting: Quam sed rem quia consequuntur ut. + url: Facere sint totam sapiente vitae numquam ut. + required: + - brief + LfxV2CampaignServiceBriefsUpdateCampaignRequestBody: + title: LfxV2CampaignServiceBriefsUpdateCampaignRequestBody + type: object + properties: + campaign: + $ref: '#/definitions/CampaignUpdateInput' + example: + campaign: + campaign_name: Eos doloribus autem corrupti sed voluptatum laborum. + config: Perferendis qui maiores aut reiciendis. + status: Suscipit quas sunt eos nulla. + required: + - campaign LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody: title: LfxV2CampaignServiceConnectionsCreateGoogleAdsRequestBody type: object @@ -3001,10 +4189,10 @@ definitions: label: TLF Main login_customer_id: "9746983954" credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. required: - config - credentials @@ -3018,14 +4206,14 @@ definitions: $ref: '#/definitions/HubspotCredentials' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Vitae vel omnis autem accusamus mollitia. required: - config - credentials @@ -3040,10 +4228,10 @@ definitions: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Temporibus et provident maiores neque. org_id: "208777" credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Illo minima eaque earum. required: - config - credentials @@ -3058,12 +4246,12 @@ definitions: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. required: - config - credentials @@ -3077,14 +4265,14 @@ definitions: $ref: '#/definitions/MicrosoftAdsCredentials' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Officiis aut fugiat optio ut. + client_secret: Et voluptas tenetur nobis facere. + developer_token: Blanditiis mollitia dolores illo nostrum magnam. + refresh_token: Quo corporis recusandae dicta quibusdam. required: - config - credentials @@ -3099,11 +4287,11 @@ definitions: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Laboriosam qui id enim. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. required: - config - credentials @@ -3118,13 +4306,13 @@ definitions: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. required: - config - credentials @@ -3136,10 +4324,10 @@ definitions: $ref: '#/definitions/GoogleAdsCredentials' example: credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialHubspotRequestBody: @@ -3150,7 +4338,7 @@ definitions: $ref: '#/definitions/HubspotCredentials' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Vitae vel omnis autem accusamus mollitia. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialLinkedinAdsRequestBody: @@ -3161,7 +4349,7 @@ definitions: $ref: '#/definitions/LinkedinAdsCredentials' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Illo minima eaque earum. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialMetaAdsRequestBody: @@ -3172,8 +4360,8 @@ definitions: $ref: '#/definitions/MetaAdsCredentials' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialMicrosoftAdsRequestBody: @@ -3184,10 +4372,10 @@ definitions: $ref: '#/definitions/MicrosoftAdsCredentials' example: credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Officiis aut fugiat optio ut. + client_secret: Et voluptas tenetur nobis facere. + developer_token: Blanditiis mollitia dolores illo nostrum magnam. + refresh_token: Quo corporis recusandae dicta quibusdam. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialRedditAdsRequestBody: @@ -3198,9 +4386,9 @@ definitions: $ref: '#/definitions/RedditAdsCredentials' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. required: - credentials LfxV2CampaignServiceConnectionsSetCredentialTwitterAdsRequestBody: @@ -3211,10 +4399,10 @@ definitions: $ref: '#/definitions/TwitterAdsCredentials' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. required: - credentials LfxV2CampaignServiceConnectionsUpdateGoogleAdsRequestBody: @@ -3238,12 +4426,12 @@ definitions: $ref: '#/definitions/HubspotConnectionConfig' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. required: - config LfxV2CampaignServiceConnectionsUpdateLinkedinAdsRequestBody: @@ -3255,7 +4443,7 @@ definitions: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Temporibus et provident maiores neque. org_id: "208777" required: - config @@ -3268,9 +4456,9 @@ definitions: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. required: - config LfxV2CampaignServiceConnectionsUpdateMicrosoftAdsRequestBody: @@ -3281,9 +4469,9 @@ definitions: $ref: '#/definitions/MicrosoftAdsConnectionConfig' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. required: - config LfxV2CampaignServiceConnectionsUpdateRedditAdsRequestBody: @@ -3295,7 +4483,7 @@ definitions: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Laboriosam qui id enim. required: - config LfxV2CampaignServiceConnectionsUpdateTwitterAdsRequestBody: @@ -3307,8 +4495,8 @@ definitions: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. required: - config LinkedinAdsConnection: @@ -3318,27 +4506,27 @@ definitions: account_id: type: string description: Provider account identifier - example: Vel blanditiis sunt ut facere. + example: Quo vel iste aut cum voluptatem. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Ipsa nihil ad hic repellendus nobis voluptatem. + example: Quo ullam ad iusto quos. label: type: string description: Optional friendly name - example: Rerum cupiditate. + example: Tenetur ut. org_id: type: string description: LinkedIn organization URN id - example: Vel molestiae consequatur. + example: Facere voluptas quis nihil. project_id: type: string description: Owning project - example: Vero et voluptas accusamus sunt. + example: Est placeat explicabo aliquid. status: type: string description: Connection status @@ -3351,17 +4539,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 4079703323268843140 + example: 1833221466395792722 format: int64 example: - account_id: Et corporis officia sint. + account_id: Vero ut et neque soluta distinctio voluptas. has_credentials: false - id: Quidem est non tenetur dolore. - label: Cupiditate voluptatem. - org_id: Quia nulla autem ratione. - project_id: Dolores commodi aperiam voluptatibus. - status: error - version: 874315130556833044 + id: Rem asperiores eligendi vero. + label: Facere quas qui aut rerum. + org_id: Fugit eum. + project_id: Voluptatem fugiat et. + status: active + version: 5717670313645740058 required: - id - project_id @@ -3380,14 +4568,14 @@ definitions: label: type: string description: Optional friendly name - example: In mollitia a magni. + example: Necessitatibus voluptatem incidunt et totam esse rerum. org_id: type: string description: LinkedIn organization URN id example: "208777" example: account_id: "538170226" - label: Consequatur ea deserunt tempora aut assumenda. + label: Dolorem delectus. org_id: "208777" required: - account_id @@ -3399,10 +4587,10 @@ definitions: access_token: type: string description: OAuth access token - example: Occaecati eum tenetur. + example: Voluptatem incidunt cum. description: LinkedIn Ads credential. Write-only; never returned. example: - access_token: Inventore aliquid voluptatibus iste mollitia. + access_token: Occaecati quaerat eum quia a excepturi. required: - access_token MetaAdsConnection: @@ -3412,11 +4600,11 @@ definitions: account_id: type: string description: Provider account identifier - example: Asperiores ipsum quam. + example: Ad dolorem et. app_id: type: string description: Meta app ID - example: Cum rem architecto vel quibusdam vel autem. + example: Impedit unde voluptatem quia ab recusandae praesentium. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3424,23 +4612,23 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: Libero atque est non praesentium. + example: Ut possimus quidem eos culpa quaerat. label: type: string description: Optional friendly name - example: Id tenetur exercitationem aperiam incidunt. + example: Inventore ratione id vel iusto optio repellendus. page_id: type: string description: Facebook page ID - example: Qui suscipit rerum quo eligendi tempora. + example: Sunt aut. project_id: type: string description: Owning project - example: Saepe sit et sed qui dolorem reiciendis. + example: Inventore aut molestiae voluptas hic. status: type: string description: Connection status - example: active + example: error enum: - active - inactive @@ -3449,18 +4637,18 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 5968285761891001183 + example: 6328500858220715511 format: int64 example: - account_id: Quibusdam aliquam consequuntur. - app_id: Deleniti earum magni quibusdam. - has_credentials: false - id: Id facere maxime. - label: Maxime ut possimus expedita. - page_id: Facilis facilis aut suscipit aut odio iste. - project_id: Hic delectus. - status: active - version: 6674224742141168677 + account_id: Iusto fugiat ad et. + app_id: Ea eum. + has_credentials: true + id: Est magni odio aut omnis vel. + label: Ullam consequatur ut eveniet tempore est. + page_id: Mollitia vel. + project_id: Iure ducimus voluptatem. + status: error + version: 6284855428062318470 required: - id - project_id @@ -3479,20 +4667,20 @@ definitions: app_id: type: string description: Meta app ID - example: Harum provident quia distinctio ea. + example: Tempore rem quo fuga accusantium voluptas ratione. label: type: string description: Optional friendly name - example: Tenetur velit quaerat aut. + example: Qui qui. page_id: type: string description: Facebook page ID - example: Aut et. + example: Repudiandae quae aut. example: account_id: act_193556282970417 - app_id: Blanditiis atque rerum et officiis sapiente et. - label: Rem harum provident provident quia laboriosam magnam. - page_id: Hic et sed. + app_id: Perspiciatis quam iste possimus ipsam. + label: Esse et labore earum. + page_id: Numquam optio possimus velit quod. required: - account_id MetaAdsCredentials: @@ -3502,15 +4690,15 @@ definitions: access_token: type: string description: Meta access token - example: Itaque dolores in hic rem. + example: Enim id. app_secret: type: string description: Meta app secret - example: Eligendi ea vel consequatur nihil. + example: Eveniet quia voluptatem impedit cumque doloribus. description: Meta Ads credential. Write-only; never returned. example: - access_token: Vero commodi est qui asperiores velit enim. - app_secret: Expedita ut minus ut est recusandae aspernatur. + access_token: Vel necessitatibus vel voluptates tenetur veniam repellat. + app_secret: Minima vitae illo cumque optio impedit. required: - access_token - app_secret @@ -3521,31 +4709,31 @@ definitions: account_id: type: string description: Provider account identifier - example: Ipsum voluptatem et quam beatae laborum omnis. + example: Repudiandae facilis blanditiis libero nam. customer_id: type: string description: Microsoft Advertising customer ID - example: Rem suscipit similique ipsam aut quis. + example: Quam quia possimus deserunt et quidem. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Repudiandae voluptatem vitae iste maxime modi laborum. + example: Qui et assumenda quas. label: type: string description: Optional friendly name - example: Magnam fugit. + example: Delectus blanditiis magnam sed iure amet. project_id: type: string description: Owning project - example: Dignissimos necessitatibus consequuntur. + example: Quisquam animi. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -3554,17 +4742,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 7512765750150729777 + example: 5244135457294865936 format: int64 example: - account_id: Nobis nam. - customer_id: Reiciendis pariatur quisquam. + account_id: Officiis aliquid explicabo eaque ab. + customer_id: Ea iusto eveniet velit sint odit sed. has_credentials: true - id: Laudantium dolore. - label: Non quidem doloribus debitis itaque itaque dolores. - project_id: Possimus nihil qui enim ratione voluptatem eos. - status: error - version: 7616558197057713508 + id: Laudantium molestiae omnis doloremque consequuntur perspiciatis modi. + label: Vero et atque in impedit velit omnis. + project_id: Quae sed. + status: inactive + version: 187687687190489160 required: - id - project_id @@ -3579,19 +4767,19 @@ definitions: account_id: type: string description: Microsoft Advertising account ID - example: Aspernatur facere. + example: Esse voluptate optio quis quae quam. customer_id: type: string description: Microsoft Advertising customer ID - example: Voluptatem dicta rerum sed. + example: Odio quia omnis commodi. label: type: string description: Optional friendly name - example: Aspernatur ea voluptatem eum voluptas eveniet assumenda. + example: Eos nemo cupiditate. example: - account_id: Quia vero quis consequuntur id. - customer_id: Quo illum cum quod. - label: Quia possimus dolore vel qui. + account_id: Illo porro velit perferendis. + customer_id: Ipsum aut voluptas molestias unde. + label: Pariatur nostrum. required: - account_id MicrosoftAdsCredentials: @@ -3601,25 +4789,25 @@ definitions: client_id: type: string description: OAuth client id - example: Ut fugiat magnam quia. + example: Voluptas ut vero autem consequuntur optio omnis. client_secret: type: string description: OAuth client secret - example: Ut tempora consequuntur cumque repudiandae omnis ut. + example: Pariatur omnis ut velit dolorem dolor. developer_token: type: string description: Microsoft Advertising developer token - example: Est aut labore sint natus. + example: Nihil ut. refresh_token: type: string description: OAuth refresh token - example: Distinctio beatae. + example: Omnis in qui. description: Microsoft Advertising OAuth credential set. Write-only; never returned. example: - client_id: Ducimus debitis nesciunt quas quibusdam occaecati cum. - client_secret: Iste voluptatem et voluptatem ut ut. - developer_token: Quia sed omnis quaerat aut. - refresh_token: Nostrum laborum minima. + client_id: Mollitia nulla. + client_secret: Minus voluptatem ab delectus porro. + developer_token: Accusamus quia velit commodi. + refresh_token: Veniam molestias. required: - client_id - client_secret @@ -3644,6 +4832,34 @@ definitions: required: - code - message + PlatformResult: + title: PlatformResult + type: object + properties: + campaign_id: + type: string + description: Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost. + example: Nostrum quia laboriosam ut nobis. + error: + type: string + description: Failure reason (present when not ok) + example: Velit quis. + ok: + type: boolean + description: Whether the campaign was created (or reused) successfully + example: true + platform: + type: string + description: Platform this result is for + example: Perferendis culpa voluptatem nostrum eaque deleniti. + example: + campaign_id: Architecto quae error. + error: Placeat et minus et. + ok: true + platform: Aliquam animi. + required: + - platform + - ok PreconditionFailedError: title: PreconditionFailedError type: object @@ -3689,7 +4905,7 @@ definitions: account_id: type: string description: Provider account identifier - example: Ipsam temporibus sint et necessitatibus dolorem ut. + example: Voluptate dolorum id ut molestias id. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3697,19 +4913,19 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: Fugit voluptas molestias architecto dicta quidem illo. + example: Aut repudiandae voluptatibus odit autem. label: type: string description: Optional friendly name - example: Quia aut sed fugiat adipisci eos rerum. + example: Et earum id. project_id: type: string description: Owning project - example: Doloremque et qui perferendis. + example: Dicta qui. status: type: string description: Connection status - example: active + example: deleted enum: - active - inactive @@ -3718,16 +4934,16 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 1442766184420227830 + example: 7470923779752943348 format: int64 example: - account_id: Voluptates neque expedita. - has_credentials: false - id: Qui sed fugiat. - label: Sit iusto. - project_id: Veniam debitis aut similique ullam temporibus qui. - status: active - version: 3889986261433875806 + account_id: Vitae nemo sint et consequatur vel quisquam. + has_credentials: true + id: Non assumenda molestiae. + label: Autem aliquam voluptatibus assumenda mollitia molestias eos. + project_id: Impedit vitae molestiae. + status: error + version: 3850958169697422368 required: - id - project_id @@ -3746,10 +4962,10 @@ definitions: label: type: string description: Optional friendly name - example: Quisquam ut eius. + example: Sint possimus. example: account_id: t2_gv9wtbfa - label: Ipsum nihil cumque repellendus sint soluta. + label: Dolorem neque hic voluptatem ad similique sunt. required: - account_id RedditAdsCredentials: @@ -3759,20 +4975,20 @@ definitions: client_id: type: string description: OAuth client id - example: Sed quibusdam reiciendis qui vel. + example: Doloremque dicta. client_secret: type: string description: OAuth client secret - example: Amet et eaque. + example: Ratione impedit recusandae dolor consequatur. refresh_token: type: string description: OAuth refresh token - example: Consequatur iste sed illo adipisci sunt. + example: Vel temporibus sunt nulla esse sed. description: Reddit Ads OAuth credential. Write-only; never returned. example: - client_id: Soluta consectetur porro quo quia incidunt. - client_secret: Aperiam et ipsam vitae. - refresh_token: Velit est quis aliquid aliquam. + client_id: Voluptatem beatae repellat veritatis perferendis veritatis. + client_secret: Facere sed ea qui pariatur adipisci. + refresh_token: Enim esse dolores. required: - client_id - client_secret @@ -3784,11 +5000,11 @@ definitions: account_id: type: string description: Provider account identifier - example: Vitae quae debitis sunt aperiam ex voluptatibus. + example: In autem neque nihil laborum omnis. funding_instrument_id: type: string description: Funding instrument for the ad account - example: In fuga ut. + example: Dignissimos consequatur reiciendis delectus enim quas. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3796,19 +5012,19 @@ definitions: id: type: string description: Service-generated connection UUID (not used in paths) - example: Voluptate nisi natus sint. + example: Iste exercitationem voluptate quia esse nihil sed. label: type: string description: Optional friendly name - example: Ut dolor velit. + example: Neque maiores quo aliquam in rem. project_id: type: string description: Owning project - example: Beatae aliquam quia officiis hic ea. + example: Est perferendis eos et cumque nulla. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -3817,17 +5033,17 @@ definitions: version: type: integer description: Optimistic-concurrency version - example: 6056629402053782931 + example: 3490326709734661888 format: int64 example: - account_id: Non possimus quos voluptatem doloremque consequatur deleniti. - funding_instrument_id: Fuga illo dolorem sit ex commodi sapiente. - has_credentials: false - id: Iusto quidem laudantium. - label: Quaerat ut neque expedita nostrum. - project_id: Ratione consequuntur aut et corrupti. - status: error - version: 5145245548849296683 + account_id: Rerum et. + funding_instrument_id: Inventore quasi officiis aut omnis itaque. + has_credentials: true + id: Porro eius est porro eos. + label: Eveniet vel aut. + project_id: Quia nihil quo repudiandae laboriosam non. + status: inactive + version: 1231088733548867560 required: - id - project_id @@ -3846,15 +5062,15 @@ definitions: funding_instrument_id: type: string description: Funding instrument for the ad account - example: Excepturi dolorem autem ipsa autem provident. + example: Provident velit voluptatibus voluptatem. label: type: string description: Optional friendly name - example: Illum tenetur numquam. + example: Vero voluptatibus quis. example: account_id: 8r7gb - funding_instrument_id: Quis et odio tenetur optio. - label: Iure quo culpa. + funding_instrument_id: Dolores vero. + label: Dolorem qui mollitia sequi non. required: - account_id TwitterAdsCredentials: @@ -3864,25 +5080,25 @@ definitions: access_token: type: string description: OAuth 1.0a access token - example: Veritatis excepturi veniam maxime incidunt asperiores voluptatem. + example: Et voluptatem aut est voluptatibus placeat. access_token_secret: type: string description: OAuth 1.0a access token secret - example: Nostrum dolorum iure eaque animi. + example: Pariatur perferendis explicabo expedita et velit accusamus. consumer_key: type: string description: OAuth 1.0a consumer key - example: Ut sint. + example: Aut quibusdam repellendus qui numquam explicabo. consumer_secret: type: string description: OAuth 1.0a consumer secret - example: Iste eveniet laudantium rerum. + example: Quaerat minima a voluptatem. description: X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned. example: - access_token: Error necessitatibus et autem doloribus est. - access_token_secret: Maxime labore illo maxime provident. - consumer_key: Error laudantium. - consumer_secret: Similique odio adipisci dolore iste. + access_token: Recusandae totam soluta. + access_token_secret: Praesentium libero. + consumer_key: Deleniti placeat rerum. + consumer_secret: Nulla eos. required: - consumer_key - consumer_secret diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index cfa12163..1ad9d842 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-campaign-service"}],"paths":{"/projects/{project_id}/connection-google-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Inventore ratione id vel iusto optio repellendus."},"example":"Rerum eveniet nam sunt."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection3"},"example":{"account_id":"Vitae qui.","has_credentials":true,"id":"Minima autem libero natus id esse.","label":"Odit eum commodi autem expedita dolorem.","login_customer_id":"In laborum vel.","project_id":"Sed adipisci eveniet et aut.","status":"active","version":4120562596096857294}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Occaecati quaerat eum quia a excepturi."},"example":"Inventore aut molestiae voluptas hic."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection2"},"example":{"account_id":"Corrupti reprehenderit et non totam perspiciatis.","has_credentials":false,"id":"Harum laborum est adipisci sint.","label":"Ipsa ex.","login_customer_id":"Perspiciatis voluptatibus architecto velit.","project_id":"Est impedit et tempore provident.","status":"error","version":6280538112072962831}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Inventore impedit unde voluptatem quia ab recusandae."},"example":"Vel omnis iure."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection4"},"example":{"account_id":"Ut commodi asperiores.","has_credentials":false,"id":"Dignissimos non ut sed.","label":"Perferendis nam voluptatibus totam.","login_customer_id":"Voluptatem similique.","project_id":"Deleniti quam.","status":"inactive","version":2550395787104322115}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Deserunt ab itaque amet numquam.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Necessitatibus quaerat minima a voluptatem ipsa et."},"example":"Pariatur perferendis explicabo expedita et velit accusamus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection3"},"example":{"account_id":"Perspiciatis saepe consequatur doloribus sequi et.","brand_kit":"Distinctio vel.","has_credentials":true,"id":"Nulla ut laboriosam.","label":"Omnis suscipit corrupti reiciendis.","portal_id":"Nihil quia omnis eaque aperiam eum.","project_id":"Rerum delectus velit.","sender_email":"Quam et voluptatibus illum dolorem.","sender_name":"Eum dolorum.","status":"active","version":3995988906528985875}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHubspotRequestBody"},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Sequi non et."},"example":"Quibusdam repellendus qui numquam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection2"},"example":{"account_id":"Voluptates iste autem veniam et enim unde.","brand_kit":"Blanditiis quidem qui iusto sed.","has_credentials":false,"id":"Nesciunt necessitatibus ex ea ullam corrupti sunt.","label":"Modi asperiores ut libero vel.","portal_id":"Rem quia consequuntur ut.","project_id":"Omnis accusantium ad.","sender_email":"Consequatur quos atque voluptates quo sit.","sender_name":"Dolor molestias ipsa dolorum est.","status":"inactive","version":1691702820919785934}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHubspotRequestBody"},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Deleniti placeat rerum."},"example":"Recusandae totam soluta."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection4"},"example":{"account_id":"Aut ratione sunt nostrum tenetur.","brand_kit":"Ab laboriosam quis sapiente ut sit.","has_credentials":true,"id":"Ipsum quaerat delectus cupiditate sit et.","label":"Magni et rerum sed voluptatum.","portal_id":"Fugit enim et sint consectetur ea minus.","project_id":"Enim est occaecati sed non dolorem distinctio.","sender_email":"Ut laborum nostrum veniam qui rerum.","sender_name":"Eum fugit repudiandae magnam ut.","status":"active","version":2378889005955898844}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialHubspotRequestBody"},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Tempora error rerum quae nihil.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ea eum."},"example":"Repudiandae quae aut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection3"},"example":{"account_id":"Quis repellat tenetur.","has_credentials":false,"id":"Et quam veritatis consequatur sed.","label":"Ipsum fuga deleniti consequatur.","org_id":"Natus laudantium quisquam.","project_id":"Tempora aut incidunt incidunt dolorem eaque et.","status":"inactive","version":2296210645404105233}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptatem doloremque ullam consequatur ut."},"example":"Ipsa magnam cum veritatis mollitia vel."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection2"},"example":{"account_id":"Totam quasi vero.","has_credentials":false,"id":"Eum quia molestiae.","label":"Est quos.","org_id":"Est est id repellat quod deleniti cupiditate.","project_id":"Rerum delectus tempora culpa.","status":"error","version":2086671994798543080}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Tempore rem quo fuga accusantium voluptas ratione."},"example":"Numquam optio possimus velit quod."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection4"},"example":{"account_id":"Ipsa rerum odit et nostrum veritatis dolores.","has_credentials":false,"id":"Aut et voluptas ut officiis.","label":"Debitis voluptatem quam occaecati.","org_id":"Et tenetur sequi dolorum porro.","project_id":"In autem tempore ut esse.","status":"deleted","version":7961829707957443728}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialLinkedinAdsRequestBody"},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Dolorum voluptates placeat rerum.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Vel necessitatibus vel voluptates tenetur veniam repellat."},"example":"Aut repudiandae voluptatibus odit autem."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection3"},"example":{"account_id":"Voluptatem cupiditate voluptas aliquam facilis iste sed.","app_id":"A sed exercitationem ut.","has_credentials":false,"id":"Expedita unde molestiae.","label":"Velit ullam.","page_id":"Voluptatum a.","project_id":"Voluptas dolor et incidunt pariatur quod odit.","status":"active","version":6376430019451136769}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Perspiciatis quam iste possimus ipsam."},"example":"Eveniet quia voluptatem impedit cumque doloribus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection2"},"example":{"account_id":"Fuga ut expedita voluptatum sit.","app_id":"Quia ut atque.","has_credentials":true,"id":"Quis perspiciatis.","label":"Similique vel nobis non molestias.","page_id":"Velit et.","project_id":"Nesciunt unde ullam occaecati maxime.","status":"active","version":8123871071866488705}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Dicta qui."},"example":"Voluptate dolorum id ut molestias id."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection4"},"example":{"account_id":"Quos quo eum dignissimos iure praesentium.","app_id":"Voluptas ratione delectus ut adipisci.","has_credentials":false,"id":"Doloremque itaque eius magni autem.","label":"Ex tempore.","page_id":"Qui mollitia perferendis eveniet voluptas est et.","project_id":"Ullam tenetur quia totam magni sunt repellat.","status":"inactive","version":7772855560046758577}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialMetaAdsRequestBody"},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Aliquam animi unde.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Non eos."},"example":"Enim voluptatem inventore quasi."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection3"},"example":{"account_id":"Optio dolorem omnis.","customer_id":"Rerum adipisci minima quibusdam autem accusantium.","has_credentials":false,"id":"Veniam eum ut libero quibusdam debitis.","label":"Commodi quam voluptates.","project_id":"Ut consectetur.","status":"deleted","version":6203614951491514230}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Delectus enim quas id porro."},"example":"Quo repudiandae."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection2"},"example":{"account_id":"Exercitationem rerum iste ducimus.","customer_id":"Quidem velit est explicabo est sed.","has_credentials":true,"id":"Modi rerum sint.","label":"Dolorem dolor fugiat quia.","project_id":"Labore iste voluptates quos.","status":"inactive","version":8271015532862321776}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Aut omnis itaque ut vero voluptatibus."},"example":"Vero dolorem qui."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection4"},"example":{"account_id":"Maxime fugiat illo.","customer_id":"Minus est ut.","has_credentials":false,"id":"Tempora recusandae.","label":"Reiciendis architecto atque maxime soluta dolorem ducimus.","project_id":"Itaque harum.","status":"deleted","version":6177609042943350629}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Ullam fugiat.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Autem aliquam voluptatibus assumenda mollitia molestias eos."},"example":"Fugit eum et sint possimus omnis."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection3"},"example":{"account_id":"Amet iusto provident sequi est.","has_credentials":true,"id":"Voluptas non accusantium.","label":"Sit dolorum excepturi quibusdam.","project_id":"Architecto consequatur enim magnam eius voluptas voluptatibus.","status":"deleted","version":1322493338651594921}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ipsa aut."},"example":"Impedit vitae molestiae."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection2"},"example":{"account_id":"Ut ea alias.","has_credentials":false,"id":"Explicabo cupiditate rerum.","label":"Hic vero et.","project_id":"Incidunt ipsum est perspiciatis soluta voluptas est.","status":"inactive","version":347677279926224703}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Neque hic voluptatem ad similique sunt."},"example":"Ratione impedit recusandae dolor consequatur."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection4"},"example":{"account_id":"Saepe pariatur.","has_credentials":true,"id":"Vero quam.","label":"Eveniet soluta adipisci.","project_id":"Neque tempore voluptatibus quo quod nesciunt.","status":"active","version":779913646680513111}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialRedditAdsRequestBody"},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Repudiandae veniam soluta alias natus aut.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Enim esse dolores."},"example":"Est perferendis eos et cumque nulla."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection3"},"example":{"account_id":"Et tempore.","funding_instrument_id":"Est et id laudantium magnam ratione.","has_credentials":false,"id":"Ipsum deserunt ut.","label":"Ut nihil quo tenetur doloribus fugiat natus.","project_id":"Sed ea.","status":"error","version":6360932954176398686}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Vel temporibus sunt nulla esse sed."},"example":"Facere sed ea qui pariatur adipisci."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection2"},"example":{"account_id":"Totam sequi.","funding_instrument_id":"Labore ut iusto dolores.","has_credentials":false,"id":"Dolor ut.","label":"Porro sed minus illo rerum ut.","project_id":"Veniam maiores dolorum sit et.","status":"active","version":5887915076445143491}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Neque maiores quo aliquam in rem."},"example":"Voluptatem possimus veniam dignissimos consequatur."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection4"},"example":{"account_id":"Est dolorem eos amet dolorem sed.","funding_instrument_id":"Quia porro.","has_credentials":false,"id":"Ab nobis fuga atque inventore fugiat.","label":"Quod blanditiis et.","project_id":"Laboriosam maxime facere culpa modi.","status":"error","version":796228352115418506}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialTwitterAdsRequestBody"},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Quo aliquid nobis.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"ConflictError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Rerum sunt eum et qui."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":true}},"example":{"message":"Ut quia harum.","ok":false},"required":["ok"]},"CreateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["config","credentials"]},"CreateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"},"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."},"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["config","credentials"]},"CreateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"},"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["config","credentials"]},"CreateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."},"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["config","credentials"]},"CreateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."},"credentials":{"client_id":"Voluptates odit consequatur quis.","client_secret":"Est voluptatum enim earum et doloribus.","developer_token":"Id ducimus eius et laboriosam omnis vitae.","refresh_token":"Ea consequatur et ut."}},"required":["config","credentials"]},"CreateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."},"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["config","credentials"]},"CreateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."},"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["config","credentials"]},"GoogleAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptatem sed quia excepturi."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptas ullam dolores rem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Labore laudantium."},"label":{"type":"string","description":"Optional friendly name","example":"Eos voluptatibus."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Officia quidem non qui voluptates natus cum."},"project_id":{"type":"string","description":"Owning project","example":"Facilis reiciendis dignissimos consequatur iste qui minima."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1885605882290356239,"format":"int64"}},"example":{"account_id":"Iusto velit quo voluptatibus modi reiciendis aliquam.","etag":"Sunt recusandae similique.","has_credentials":false,"id":"Autem recusandae totam.","label":"Odit necessitatibus veritatis amet ut rerum quidem.","login_customer_id":"Iusto quia.","project_id":"Rerum nisi.","status":"error","version":4043658872882310311},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"GoogleAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Error ipsam maiores et maiores laudantium aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aliquid harum quis."},"label":{"type":"string","description":"Optional friendly name","example":"Mollitia odio et dolor et consectetur."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Sint inventore iste iure ut."},"project_id":{"type":"string","description":"Owning project","example":"Rerum eaque eius est rerum."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1681601347963415661,"format":"int64"}},"example":{"account_id":"Ipsa et dolore.","has_credentials":true,"id":"Facilis suscipit.","label":"Ad inventore voluptatum eligendi quasi.","login_customer_id":"Placeat unde distinctio ducimus qui.","project_id":"Porro voluptate.","status":"inactive","version":1820304569870851201},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nihil corrupti repellat eius."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Dignissimos non sed omnis officiis et."},"label":{"type":"string","description":"Optional friendly name","example":"Magni repudiandae."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Delectus quam et eos."},"project_id":{"type":"string","description":"Owning project","example":"Laboriosam qui id enim."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8134511901185890068,"format":"int64"}},"example":{"account_id":"Illo vitae occaecati omnis.","has_credentials":false,"id":"Rem aut fuga.","label":"Cupiditate ut illo dolor quaerat totam.","login_customer_id":"Explicabo possimus consequatur.","project_id":"Est dolor ut corporis.","status":"active","version":299946558233755685},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"A necessitatibus molestiae."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Quos hic doloribus."},"label":{"type":"string","description":"Optional friendly name","example":"Eum et sint corrupti eos."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Dolorem qui explicabo consequuntur repellat non suscipit."},"project_id":{"type":"string","description":"Owning project","example":"Neque autem ex eos."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7747776820204091337,"format":"int64"}},"example":{"account_id":"Sit est dolores est et.","has_credentials":false,"id":"Sit tempore.","label":"Ut quo iure.","login_customer_id":"Velit atque laudantium ullam rerum.","project_id":"Magnam ut similique ea minima magnam.","status":"deleted","version":7309957715046801903},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Ut aut."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Sit similique ut minima ut."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Voluptatum officia quod error amet quia inventore."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Voluptas expedita omnis ex."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Non ut voluptas et accusamus natus id.","client_secret":"Et officia sequi et quam.","developer_token":"Vero quisquam.","refresh_token":"Quia enim illo et eligendi."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Delectus voluptatem iste nisi in."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Nostrum corporis quasi magnam."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Dolor occaecati earum iusto rerum sit cupiditate."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Nam quia aperiam sunt est."},"label":{"type":"string","description":"Optional friendly name","example":"Natus expedita aspernatur consequuntur quod ducimus molestias."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Iste qui rerum est sed sed similique."},"project_id":{"type":"string","description":"Owning project","example":"Libero ut est rerum est occaecati et."},"sender_email":{"type":"string","description":"Default sender address","example":"Ea maiores quidem molestiae deleniti omnis."},"sender_name":{"type":"string","description":"Default sender name","example":"Expedita voluptatum commodi fuga ipsa."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3681253484013323435,"format":"int64"}},"example":{"account_id":"Neque facilis est qui possimus eligendi.","brand_kit":"Voluptas eius error autem est est distinctio.","etag":"Et nobis voluptas dignissimos exercitationem illum.","has_credentials":false,"id":"Qui quidem exercitationem.","label":"Sed numquam necessitatibus laudantium est ut.","portal_id":"Quisquam ex est facilis qui ducimus.","project_id":"Ipsa quo vel architecto quas eligendi impedit.","sender_email":"Ut culpa excepturi.","sender_name":"Officiis quia quisquam corrupti.","status":"inactive","version":8080806383587730069},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"HubspotConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Magni omnis."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Perferendis culpa voluptatem nostrum eaque deleniti."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Esse voluptate."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptatum et impedit quod."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Est repellat."},"project_id":{"type":"string","description":"Owning project","example":"Consectetur et culpa quaerat vel."},"sender_email":{"type":"string","description":"Default sender address","example":"Esse sit id incidunt mollitia non sed."},"sender_name":{"type":"string","description":"Default sender name","example":"Minima est distinctio accusamus."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7333781901734102627,"format":"int64"}},"example":{"account_id":"Aliquam animi.","brand_kit":"Eos iusto fugit.","has_credentials":true,"id":"Quo nostrum quia.","label":"Velit quis.","portal_id":"Error dolor placeat et.","project_id":"Ut nobis.","sender_email":"Et esse fugiat aliquid aspernatur voluptatem in.","sender_name":"Consequatur et dolorem.","status":"error","version":2888074467672743464},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptas omnis quidem dolor ipsum voluptas sed."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Tempora dolor quis totam hic quibusdam eveniet."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Asperiores quidem."},"label":{"type":"string","description":"Optional friendly name","example":"Nesciunt adipisci odit corrupti laudantium consequatur hic."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Dolorem necessitatibus neque consectetur occaecati earum."},"project_id":{"type":"string","description":"Owning project","example":"Est nostrum atque."},"sender_email":{"type":"string","description":"Default sender address","example":"Ut sint non eum expedita non."},"sender_name":{"type":"string","description":"Default sender name","example":"Sit voluptatem ad ab."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3980984224261423037,"format":"int64"}},"example":{"account_id":"Quidem omnis totam eos non molestias.","brand_kit":"Ducimus id sint eius aut.","has_credentials":false,"id":"Doloribus voluptas mollitia doloremque totam.","label":"Quia doloremque commodi rerum.","portal_id":"Recusandae illum accusamus debitis.","project_id":"Veniam et ducimus quos amet.","sender_email":"Praesentium sint laudantium error.","sender_name":"Nihil iure officiis nemo quia.","status":"inactive","version":6150421422111226538},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ab quo ullam ad."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Aperiam facere voluptas quis nihil."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Et quia ex et alias ea."},"label":{"type":"string","description":"Optional friendly name","example":"Laborum maiores est ea ducimus ipsum dolorem."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Placeat explicabo."},"project_id":{"type":"string","description":"Owning project","example":"Quas quis corporis."},"sender_email":{"type":"string","description":"Default sender address","example":"Rerum tenetur ut voluptatem quo."},"sender_name":{"type":"string","description":"Default sender name","example":"Iste aut cum voluptatem qui et."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3448886829732168782,"format":"int64"}},"example":{"account_id":"Vero ut et neque soluta distinctio voluptas.","brand_kit":"Voluptatem incidunt cum.","has_credentials":false,"id":"Rem asperiores eligendi vero.","label":"Facere quas qui aut rerum.","portal_id":"Fugit eum.","project_id":"Voluptatem fugiat et.","sender_email":"Necessitatibus voluptatem incidunt et totam esse rerum.","sender_name":"Dolorem delectus.","status":"active","version":5717670313645740058},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Quis quis reprehenderit non quae omnis qui."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Voluptatibus deserunt."},"label":{"type":"string","description":"Optional friendly name","example":"At odio incidunt distinctio sint qui."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Cumque saepe sint quidem odio ab."},"sender_email":{"type":"string","description":"Default sender address","example":"Aut est."},"sender_name":{"type":"string","description":"Default sender name","example":"Error pariatur consequatur esse."}},"example":{"account_id":"Et eligendi et numquam dolorum consequatur in.","brand_kit":"Non sequi error assumenda quam deleniti et.","label":"Necessitatibus eos.","portal_id":"Et eum est molestiae necessitatibus magni.","sender_email":"Molestiae praesentium perferendis deserunt ipsum dolorum.","sender_name":"Error nulla fuga fugit repellendus et accusamus."},"required":["account_id"]},"HubspotCredentials":{"type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Dolor officia eligendi."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Provident dicta temporibus debitis."},"required":["private_app_token"]},"InternalServerError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"LinkedinAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Cumque molestiae eos rem aut."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Non adipisci rerum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Adipisci quaerat est nobis."},"label":{"type":"string","description":"Optional friendly name","example":"Et veritatis quisquam sed ratione et."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Placeat ea odio ea eos."},"project_id":{"type":"string","description":"Owning project","example":"Fugit non fuga rem adipisci."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7868855978793741277,"format":"int64"}},"example":{"account_id":"Nobis itaque est enim magnam pariatur.","etag":"Praesentium ipsam officiis cum ipsum recusandae.","has_credentials":true,"id":"Ipsum et delectus dolorum eveniet.","label":"Maxime maxime porro enim.","org_id":"Adipisci ullam pariatur ex voluptas.","project_id":"Alias ea quibusdam.","status":"active","version":5465298508990820984},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"LinkedinAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolorum deserunt."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Rerum adipisci."},"label":{"type":"string","description":"Optional friendly name","example":"Non laudantium sunt minima repudiandae illo harum."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Eos nihil ipsam ex odio totam."},"project_id":{"type":"string","description":"Owning project","example":"Enim accusantium labore esse numquam."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2440284349670140496,"format":"int64"}},"example":{"account_id":"Omnis consequatur.","has_credentials":true,"id":"Deleniti placeat fuga aut saepe.","label":"Ut quis ut accusantium.","org_id":"Aut autem sint minus voluptatum.","project_id":"Reiciendis pariatur repellat.","status":"error","version":4719926195195381744},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quo veritatis quis delectus est."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Tempore quae."},"label":{"type":"string","description":"Optional friendly name","example":"Dolores tenetur ut illum."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Expedita accusantium vel voluptate."},"project_id":{"type":"string","description":"Owning project","example":"Consequatur dolore unde illum."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1676862666948614995,"format":"int64"}},"example":{"account_id":"Magni consequatur dignissimos itaque quia.","has_credentials":true,"id":"Repellat assumenda nesciunt distinctio eaque aliquam.","label":"Et reprehenderit dignissimos.","org_id":"Et voluptatem deserunt consequuntur omnis sed dolore.","project_id":"Veniam consectetur deserunt in hic dolorum facere.","status":"inactive","version":7072791787806691868},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Assumenda dignissimos beatae vitae aut ea dolor."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Voluptatibus quod necessitatibus non iste ad rem."},"label":{"type":"string","description":"Optional friendly name","example":"Harum corporis deleniti doloribus."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Sit architecto ut unde."},"project_id":{"type":"string","description":"Owning project","example":"Sunt qui cupiditate ullam ab suscipit et."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6055913800302101668,"format":"int64"}},"example":{"account_id":"Praesentium tenetur quas at ut necessitatibus dolor.","has_credentials":false,"id":"Sapiente possimus.","label":"Eaque voluptate reprehenderit sed voluptatem sed illo.","org_id":"Nihil id unde veritatis officiis aut fugiat.","project_id":"Voluptates atque.","status":"error","version":446959638978499075},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Cumque totam nulla aut reprehenderit."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Illum rerum error incidunt.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Aspernatur possimus."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Repellendus quo quidem."},"required":["access_token"]},"MetaAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Iusto ad animi eos doloribus autem corrupti."},"app_id":{"type":"string","description":"Meta app ID","example":"Laborum eum et quidem."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Suscipit quas sunt eos nulla."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Facere exercitationem et exercitationem."},"label":{"type":"string","description":"Optional friendly name","example":"Rerum quidem accusamus et provident provident omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Perferendis qui maiores aut reiciendis."},"project_id":{"type":"string","description":"Owning project","example":"Eaque deleniti."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1750381436080099913,"format":"int64"}},"example":{"account_id":"Ad non.","app_id":"Est voluptas sequi.","etag":"Possimus consequatur est unde.","has_credentials":false,"id":"Quasi quis ab deserunt quia magnam.","label":"Quod modi voluptas dolores commodi dicta.","page_id":"Et laudantium.","project_id":"Minus et aut consequatur non cumque.","status":"active","version":3589524561643771783},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MetaAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Nostrum tempore."},"app_id":{"type":"string","description":"Meta app ID","example":"Rerum explicabo."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ut commodi et voluptas tenetur nobis facere."},"label":{"type":"string","description":"Optional friendly name","example":"Blanditiis mollitia dolores illo nostrum magnam."},"page_id":{"type":"string","description":"Facebook page ID","example":"Deserunt repellendus corrupti itaque quo sit."},"project_id":{"type":"string","description":"Owning project","example":"Quo corporis recusandae dicta quibusdam."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6534648276901202778,"format":"int64"}},"example":{"account_id":"Perspiciatis porro.","app_id":"Deserunt quod.","has_credentials":false,"id":"Eum et ipsum expedita reprehenderit eos.","label":"Aperiam et nihil.","page_id":"Non rem error.","project_id":"Dolorum ut architecto deleniti nostrum.","status":"deleted","version":2783740423386270532},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptate necessitatibus enim et et itaque."},"app_id":{"type":"string","description":"Meta app ID","example":"Nihil aut rerum est vel."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aperiam quos perferendis nisi blanditiis eveniet."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Rerum quis et modi optio labore omnis."},"project_id":{"type":"string","description":"Owning project","example":"Ea enim ut corporis dolorum consequatur eius."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":9014435567821091487,"format":"int64"}},"example":{"account_id":"Dolor aut assumenda fugiat.","app_id":"Aut quas suscipit eos.","has_credentials":true,"id":"Rem ab corporis.","label":"Porro et.","page_id":"Inventore alias error aut maxime dicta.","project_id":"Non dolorem.","status":"deleted","version":5862072609653585869},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolor et corrupti voluptas neque vitae."},"app_id":{"type":"string","description":"Meta app ID","example":"Rerum numquam amet deserunt rem quia."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsa rerum velit et impedit."},"label":{"type":"string","description":"Optional friendly name","example":"Velit quam omnis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Mollitia est assumenda aut harum."},"project_id":{"type":"string","description":"Owning project","example":"Quaerat labore dolor."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1514261665871912761,"format":"int64"}},"example":{"account_id":"Deleniti deserunt labore.","app_id":"Molestiae deserunt voluptates.","has_credentials":true,"id":"Ipsam dolores et et qui ut.","label":"Velit esse voluptate eum laboriosam distinctio.","page_id":"Adipisci quo.","project_id":"Numquam omnis ullam facilis ab sapiente.","status":"error","version":3954861542185537606},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Eius sed qui deserunt perspiciatis nihil."},"label":{"type":"string","description":"Optional friendly name","example":"Et eveniet."},"page_id":{"type":"string","description":"Facebook page ID","example":"Corporis ut non."}},"example":{"account_id":"act_193556282970417","app_id":"Aut hic.","label":"Mollitia tempora minima voluptas.","page_id":"Facere aut facilis porro dolor ipsa."},"required":["account_id"]},"MetaAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Aperiam voluptates."},"app_secret":{"type":"string","description":"Meta app secret","example":"Vero ducimus."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Recusandae aut perferendis.","app_secret":"Est eaque ipsam harum nisi nobis."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Et nulla officia vitae."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Et sunt quis dicta voluptate aut in."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Ut asperiores quo aut."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Perspiciatis id."},"label":{"type":"string","description":"Optional friendly name","example":"Nihil nihil quae porro qui rem autem."},"project_id":{"type":"string","description":"Owning project","example":"Ut non error aut et fuga."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2431299518347433245,"format":"int64"}},"example":{"account_id":"Id temporibus et provident maiores neque quis.","customer_id":"Sunt assumenda.","etag":"Eligendi ut.","has_credentials":false,"id":"Provident nobis soluta culpa vitae.","label":"Laudantium amet veritatis rerum itaque consequatur laboriosam.","project_id":"Excepturi deleniti voluptas aperiam vel.","status":"error","version":4162198444433663334},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MicrosoftAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Earum minus autem omnis qui debitis eum."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Quidem necessitatibus voluptatem consequatur sit."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Esse officia explicabo dolor deserunt aspernatur."},"label":{"type":"string","description":"Optional friendly name","example":"Cupiditate mollitia hic vel ut."},"project_id":{"type":"string","description":"Owning project","example":"Aspernatur illo accusamus."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3699306814781450025,"format":"int64"}},"example":{"account_id":"Sed corrupti sit.","customer_id":"Consectetur tenetur eum.","has_credentials":true,"id":"Et vel beatae molestiae fuga.","label":"Aut ut.","project_id":"Vitae consequatur eos.","status":"active","version":436879921687162189},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Qui impedit temporibus eveniet eos omnis cumque."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Natus et unde."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Aut ut sit nisi architecto."},"label":{"type":"string","description":"Optional friendly name","example":"Praesentium voluptate."},"project_id":{"type":"string","description":"Owning project","example":"Rerum et recusandae possimus quis."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1288874109548033633,"format":"int64"}},"example":{"account_id":"Hic est quia pariatur odit ullam.","customer_id":"Sed quo perferendis nemo ut quisquam.","has_credentials":true,"id":"Explicabo repudiandae aut reiciendis recusandae molestias.","label":"Quod impedit accusamus incidunt minus veritatis enim.","project_id":"Vel inventore aliquid aut non eligendi accusamus.","status":"error","version":2175885598856983154},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Hic rerum accusamus velit error."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Dolorem deserunt quasi in aut nam rerum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Repellendus quos magnam."},"label":{"type":"string","description":"Optional friendly name","example":"Pariatur repellat vel."},"project_id":{"type":"string","description":"Owning project","example":"In id voluptatem nihil quaerat."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8411140861799189617,"format":"int64"}},"example":{"account_id":"Sint est sed.","customer_id":"Illum quisquam ex ipsa.","has_credentials":true,"id":"Distinctio magnam.","label":"Quia magni aut nisi est.","project_id":"Architecto deserunt vero ducimus distinctio.","status":"active","version":6452524533918462281},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Ex quo praesentium voluptas."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Est officia et."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptatem mollitia omnis doloribus qui consequatur et."}},"example":{"account_id":"Eos et architecto voluptas.","customer_id":"Vel natus provident.","label":"Numquam accusamus."},"required":["account_id"]},"MicrosoftAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Architecto itaque doloribus voluptatem consequatur quis fugiat."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Recusandae qui culpa."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Sed rerum dicta distinctio voluptas ut ut."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Accusamus et atque enim."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Fuga nostrum velit hic excepturi omnis.","client_secret":"Distinctio quos ut unde et atque.","developer_token":"In recusandae maiores et voluptatem.","refresh_token":"Odio labore pariatur nobis."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PreconditionFailedError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolores assumenda accusantium mollitia fugiat dolore similique."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Molestiae quos cum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Cum molestias fuga voluptatibus quis."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis aliquid rerum consequatur officiis velit."},"project_id":{"type":"string","description":"Owning project","example":"Aut rerum ut blanditiis dolor."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4135215091203906464,"format":"int64"}},"example":{"account_id":"Natus placeat esse est in deserunt aliquid.","etag":"Sed et odio sunt totam incidunt.","has_credentials":false,"id":"Architecto doloremque aperiam doloribus aut qui.","label":"Distinctio ipsum ab.","project_id":"Placeat cumque iure veritatis cumque nisi.","status":"error","version":6763438623066254599},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"RedditAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Assumenda harum eos eveniet ut et laboriosam."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Culpa saepe necessitatibus."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptas aperiam."},"project_id":{"type":"string","description":"Owning project","example":"Dolores enim."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6456486653687697700,"format":"int64"}},"example":{"account_id":"Cumque quae.","has_credentials":false,"id":"Qui eaque ut laudantium.","label":"Laborum aspernatur voluptate itaque fugit.","project_id":"Ratione id.","status":"deleted","version":5238309974693293664},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Dolore velit pariatur non assumenda perspiciatis dolor."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Odit consequatur."},"label":{"type":"string","description":"Optional friendly name","example":"Ut ullam."},"project_id":{"type":"string","description":"Owning project","example":"Vero soluta omnis eius."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3837313741085790100,"format":"int64"}},"example":{"account_id":"Animi molestiae aut fugit alias nisi qui.","has_credentials":false,"id":"Molestiae ea ullam minima voluptatem vel.","label":"Beatae quia minus vero at ut.","project_id":"Dolore repudiandae necessitatibus dolorem.","status":"inactive","version":8129543513726361746},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Autem iste ea in ipsa."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Deserunt dolorum nihil."},"label":{"type":"string","description":"Optional friendly name","example":"Odio odio sed minus quia quia ea."},"project_id":{"type":"string","description":"Owning project","example":"Doloremque explicabo ut."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4150548058954901958,"format":"int64"}},"example":{"account_id":"Quibusdam consequuntur dolorem fugiat.","has_credentials":true,"id":"Perspiciatis magni tempore sunt itaque.","label":"Sit beatae ut modi voluptatum veniam.","project_id":"Ut ex.","status":"error","version":5881477690177318757},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Qui molestias."}},"example":{"account_id":"t2_gv9wtbfa","label":"Molestiae minus dolor accusantium."},"required":["account_id"]},"RedditAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Numquam quidem qui."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Laboriosam possimus dolor facere velit dolores."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Alias ad voluptatem accusantium tempore impedit."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Et expedita itaque quos.","client_secret":"Quos accusantium fugit.","refresh_token":"Excepturi saepe sit vel perspiciatis neque."},"required":["client_id","client_secret","refresh_token"]},"ServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"SetCredentialGoogleAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Recusandae nam autem dicta perspiciatis quod.","client_secret":"Quo voluptatem quas culpa omnis consequuntur iste.","developer_token":"Id magnam id nihil fugiat.","refresh_token":"Inventore quisquam laudantium velit."}},"required":["credentials"]},"SetCredentialHubspotRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Numquam ut nesciunt voluptas."}},"required":["credentials"]},"SetCredentialLinkedinAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Amet necessitatibus corrupti quaerat molestiae maiores."}},"required":["credentials"]},"SetCredentialMetaAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Soluta quas voluptas minus possimus quos praesentium.","app_secret":"Repudiandae consequatur fuga dolores."}},"required":["credentials"]},"SetCredentialRedditAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Fugit qui tempora commodi reiciendis sit alias.","client_secret":"Ea enim quidem odio et sequi consequatur.","refresh_token":"Provident itaque."}},"required":["credentials"]},"SetCredentialTwitterAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Ipsum quia ad a.","access_token_secret":"Veritatis omnis excepturi.","consumer_key":"Cumque libero.","consumer_secret":"Id incidunt qui et ratione sit quaerat."}},"required":["credentials"]},"TwitterAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quisquam incidunt qui."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Quia exercitationem consequatur velit."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Quia culpa iste id sit incidunt."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Mollitia atque atque dolores accusantium non dolor."},"label":{"type":"string","description":"Optional friendly name","example":"Nobis labore dicta."},"project_id":{"type":"string","description":"Owning project","example":"Enim qui officia id necessitatibus mollitia soluta."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2620236536617279883,"format":"int64"}},"example":{"account_id":"Accusantium enim temporibus dolor ipsam.","etag":"Aut ducimus dolores.","funding_instrument_id":"Dolorum minus voluptatem.","has_credentials":false,"id":"Officiis est.","label":"Tempore nemo illum ut maiores.","project_id":"Qui quisquam voluptatibus sint quia.","status":"error","version":6092912427529457622},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"TwitterAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Delectus ex aut quia."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Corporis consequatur voluptatem voluptates."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Soluta odit odio praesentium perferendis eveniet sint."},"label":{"type":"string","description":"Optional friendly name","example":"Temporibus quis iste eaque ratione debitis nam."},"project_id":{"type":"string","description":"Owning project","example":"Quam dicta voluptate consequuntur voluptatem est."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":629084196159484927,"format":"int64"}},"example":{"account_id":"Velit fuga quos.","funding_instrument_id":"Sed aut quia nihil est quia.","has_credentials":true,"id":"Reprehenderit provident repudiandae sit.","label":"Ut sunt aliquam.","project_id":"Aut aut eos magnam quisquam et.","status":"active","version":2879571083312014203},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Velit laudantium error."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Beatae harum similique corporis id eum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fugiat est itaque."},"label":{"type":"string","description":"Optional friendly name","example":"Culpa est possimus dolores dignissimos."},"project_id":{"type":"string","description":"Owning project","example":"Fugiat aut dicta libero."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7144064222780250488,"format":"int64"}},"example":{"account_id":"Enim vitae officia dolorem odio.","funding_instrument_id":"Excepturi minima illo dolores.","has_credentials":false,"id":"Et itaque rem eos et deleniti dolores.","label":"Facere qui omnis.","project_id":"Facilis qui libero.","status":"active","version":6722562537039921847},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Non quo necessitatibus."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Laudantium nostrum earum dolores sit."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"A sint quod sunt quae et."},"label":{"type":"string","description":"Optional friendly name","example":"Ut mollitia nihil ducimus."},"project_id":{"type":"string","description":"Owning project","example":"Iure quis."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3532519562043610511,"format":"int64"}},"example":{"account_id":"At fugit dolor.","funding_instrument_id":"Non exercitationem modi possimus consequatur non.","has_credentials":false,"id":"Exercitationem accusantium nihil.","label":"Amet omnis.","project_id":"Quod alias ipsum voluptates pariatur magni.","status":"error","version":5818865715898604715},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Ut mollitia accusantium dignissimos."},"label":{"type":"string","description":"Optional friendly name","example":"Fugit provident officia officiis."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Rerum ut necessitatibus illum ratione.","label":"Consequatur qui molestiae velit ratione nesciunt et."},"required":["account_id"]},"TwitterAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"At perspiciatis quisquam perferendis."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Ut alias quis nihil dolore suscipit reprehenderit."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Id ipsa perspiciatis sunt ut velit."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Aut mollitia aut molestiae exercitationem et."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Et aut consequatur inventore enim reiciendis.","access_token_secret":"Neque ut rerum facilis quia molestiae ratione.","consumer_key":"Aut animi laudantium ullam.","consumer_secret":"Autem et id laboriosam nihil ea."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]},"UpdateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"UpdateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Temporibus voluptas inventore sit omnis.","brand_kit":"Quis facere sint totam sapiente.","label":"Sint nihil.","portal_id":"Expedita veniam beatae autem.","sender_email":"Placeat enim.","sender_name":"Nesciunt asperiores et est."}},"required":["config"]},"UpdateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Fugit voluptatem.","org_id":"208777"}},"required":["config"]},"UpdateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Eligendi vero temporibus.","label":"Non maiores quae sed quia.","page_id":"Facilis eum ipsam."}},"required":["config"]},"UpdateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Sint qui praesentium ea praesentium magni.","customer_id":"Iure dolore et et aut nam.","label":"Consequuntur neque mollitia quia."}},"required":["config"]},"UpdateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Ut est nobis aspernatur."}},"required":["config"]},"UpdateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Eveniet dicta numquam doloremque.","label":"Ea velit et assumenda recusandae enim."}},"required":["config"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"JWT issued by Heimdall; audience is this service.","scheme":"bearer"}}},"tags":[{"name":"lfx-v2-campaign-service-svc","description":"The campaign service supports Marketing Operations campaign creation and management."},{"name":"lfx-v2-campaign-service-connections","description":"Manage a project's singleton, per-provider ad-platform connections."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"LFX V2 - Campaign Service","description":"A collection of service endpoints to support Marketing Operations campaign creation and management.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for lfx-v2-campaign-service"}],"paths":{"/projects/{project_id}/briefs":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-brief lfx-v2-campaign-service-briefs","description":"Create a brief.","operationId":"lfx-v2-campaign-service-briefs#create-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBriefRequestBody"},"example":{"brief":{"copy":"Autem veniam.","event_details":"Vel autem voluptates.","event_slug":"Et est laboriosam.","keywords":"Enim unde omnis.","platforms":["Nam exercitationem consectetur sint.","Necessitatibus ex ea ullam corrupti.","Et omnis accusantium.","Iure modi asperiores ut."],"program_type":"education","targeting":"Quam sed rem quia consequuntur ut.","url":"Facere sint totam sapiente vitae numquam ut."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Hic ea et."},"example":"Et et commodi autem."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief2"},"example":{"copy":"Aperiam eum.","event_details":"Beatae soluta molestiae nihil quia omnis.","event_slug":"Fuga recusandae odio nulla ut laboriosam.","id":"Dolor molestias ipsa dolorum est.","keywords":"Quam et voluptatibus illum dolorem.","platforms":["Suscipit corrupti reiciendis et.","Saepe consequatur.","Sequi et."],"program_type":"education","project_id":"Blanditiis quidem qui iusto sed.","status":"approved","targeting":"Eum dolorum.","url":"Rerum delectus velit.","version":6878204844566876538}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}":{"delete":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"delete-brief lfx-v2-campaign-service-briefs","description":"Archive a brief (soft delete).","operationId":"lfx-v2-campaign-service-briefs#delete-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"9bc01b99-89ba-4efb-8538-2b1612fe241d","format":"uuid"},"example":"4185e754-4b3e-4607-95ac-0588f14c2d07"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-brief lfx-v2-campaign-service-briefs","description":"Get a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"e3374dee-25a0-47a6-894e-2c8dacc2c71c","format":"uuid"},"example":"9717fe0e-7c86-4c49-90fb-b9d2643146d7"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Dolores in."},"example":"Velit eligendi omnis est vel est et."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief3"},"example":{"copy":"Voluptatem voluptas omnis est tempora ipsum aut.","event_details":"Alias non sequi consequatur dolorum.","event_slug":"Et nihil.","id":"Et placeat.","keywords":"Harum et minus voluptas.","platforms":["Voluptas culpa.","Quia tenetur ducimus.","Facere harum enim consequatur id voluptatem."],"program_type":"membership","project_id":"Qui esse.","status":"draft","targeting":"Debitis labore aut.","url":"Sequi quis.","version":1292213403290328942}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-brief lfx-v2-campaign-service-briefs","description":"Replace a brief (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"cc32c8e5-5ca3-4bd4-b68b-42bdfc7b7724","format":"uuid"},"example":"9f0f6183-8826-466a-a574-30f45cedcda5"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBriefRequestBody"},"example":{"brief":{"copy":"Autem veniam.","event_details":"Vel autem voluptates.","event_slug":"Et est laboriosam.","keywords":"Enim unde omnis.","platforms":["Nam exercitationem consectetur sint.","Necessitatibus ex ea ullam corrupti.","Et omnis accusantium.","Iure modi asperiores ut."],"program_type":"education","targeting":"Quam sed rem quia consequuntur ut.","url":"Facere sint totam sapiente vitae numquam ut."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Necessitatibus fugit voluptates pariatur facilis nostrum sint."},"example":"Possimus qui est numquam omnis nihil."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief4"},"example":{"copy":"Aut et.","event_details":"Tenetur velit quaerat aut.","event_slug":"Hic delectus.","id":"Architecto vel.","keywords":"Harum provident quia distinctio ea.","platforms":["Aliquam consequuntur quo aliquid ducimus.","Facilis facilis aut suscipit aut odio iste.","Deleniti earum magni quibusdam."],"program_type":"education","project_id":"Vel autem modi id facere.","status":"draft","targeting":"Rem harum provident provident quia laboriosam magnam.","url":"Maxime ut possimus expedita.","version":4124348486962351749}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/approve":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"approve-brief lfx-v2-campaign-service-briefs","description":"Approve a brief for campaign creation.","operationId":"lfx-v2-campaign-service-briefs#approve-brief","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"fd5c9608-acd6-49e9-bb2e-de24b6d81a44","format":"uuid"},"example":"9cfebac2-eca8-4ba0-8c29-1102ebabcfea"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Animi ut aut excepturi temporibus ipsum perferendis."},"example":"Ipsum quos voluptas iure eum tempore."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief5"},"example":{"copy":"Iusto quidem laudantium.","event_details":"Est consequatur eveniet in fuga ut.","event_slug":"Velit est quis aliquid aliquam.","id":"Consectetur porro quo.","keywords":"Ratione consequuntur aut et corrupti.","platforms":["Aliquam quia officiis hic ea consequatur.","Dolor velit debitis vitae quae.","Sunt aperiam ex voluptatibus."],"program_type":"membership","project_id":"Incidunt aut aperiam et ipsam.","status":"draft","targeting":"Quaerat ut neque expedita nostrum.","url":"Voluptate nisi natus sint.","version":5518996230427104310}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns":{"post":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"create-campaigns lfx-v2-campaign-service-briefs","description":"Create campaigns across the selected platforms (async -\u003e job).","operationId":"lfx-v2-campaign-service-briefs#create-campaigns","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"fca41ae9-5cf1-4249-84ff-3c128c4d9604","format":"uuid"},"example":"edb87f72-22ea-4a62-9cc3-0f9ec1305513"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignsRequestBody"},"example":{"input":{"config":"Quia quos ut tempora consequuntur cumque repudiandae.","platforms":["microsoft-ads","microsoft-ads","linkedin-ads"]}}}}},"responses":{"202":{"description":"Accepted response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCreateResponse"},"example":{"job_id":"Enim non sint praesentium.","platforms":["Et veritatis voluptas nobis.","Doloremque architecto ea et.","Asperiores facere et exercitationem soluta possimus.","Aliquid et in."],"status":"queued"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-campaign lfx-v2-campaign-service-briefs","description":"Get one campaign under a brief; returns ETag.","operationId":"lfx-v2-campaign-service-briefs#get-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"40b44584-e849-4e18-9ef0-4ccead532f00","format":"uuid"},"example":"a5be33ff-d12e-479b-ad6a-7cc2ed678259"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"schema":{"type":"string","description":"Campaign UUID","example":"3d5136fa-42a8-45c6-bec5-cf457b12b3c2","format":"uuid"},"example":"01450dd7-92b6-418d-a29c-b38911d10766"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Mollitia nam aspernatur itaque itaque recusandae."},"example":"Omnis doloribus eius molestias animi quas ut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign2"},"example":{"brief_id":"Illum rerum error incidunt.","campaign_name":"Et veritatis quisquam sed ratione et.","id":"Quo quidem.","platform":"Adipisci quaerat est nobis.","platform_campaign_id":"Fugit non fuga rem adipisci.","project_id":"Cumque totam nulla aut reprehenderit.","status":"Cumque molestiae eos rem aut.","version":90571920890933503}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"update-campaign lfx-v2-campaign-service-briefs","description":"Replace a campaign (requires If-Match).","operationId":"lfx-v2-campaign-service-briefs#update-campaign","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"brief_id","in":"path","description":"Brief UUID","required":true,"schema":{"type":"string","description":"Brief UUID","example":"9a0492f8-2923-44bc-8d2f-16bc941b2873","format":"uuid"},"example":"a22bdbd6-342c-4b01-a5f2-015845ba9835"},{"name":"campaign_id","in":"path","description":"Campaign UUID","required":true,"schema":{"type":"string","description":"Campaign UUID","example":"fde7d17f-00f1-46be-bf9e-fe43cfa50d20","format":"uuid"},"example":"1192ee6b-f97c-47cc-85c1-a249cf3a9953"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequestBody"},"example":{"campaign":{"campaign_name":"Eos doloribus autem corrupti sed voluptatum laborum.","config":"Perferendis qui maiores aut reiciendis.","status":"Suscipit quas sunt eos nulla."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quas odio."},"example":"Perferendis asperiores."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign3"},"example":{"brief_id":"Beatae placeat cumque iure veritatis cumque.","campaign_name":"Deserunt aliquid omnis explicabo doloremque et sed.","id":"Porro architecto.","platform":"Ex distinctio ipsum ab assumenda natus.","platform_campaign_id":"Esse est.","project_id":"Aperiam doloribus aut.","status":"Odio sunt totam.","version":9047518515926792446}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-google-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Google Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-google-ads lfx-v2-campaign-service-connections","description":"Get the project's Google Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Temporibus ut sint ipsam impedit et minima."},"example":"Vero quibusdam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection3"},"example":{"account_id":"Perspiciatis id.","has_credentials":true,"id":"Officia et sint numquam accusamus.","label":"Vel natus provident.","login_customer_id":"Aut et fuga enim nihil nihil quae.","project_id":"Eos et architecto voluptas.","status":"inactive","version":270615534997019024}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-google-ads lfx-v2-campaign-service-connections","description":"Create the project's Google Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Maiores impedit consequuntur est fugit."},"example":"Accusantium et eos."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection2"},"example":{"account_id":"In recusandae maiores et voluptatem.","has_credentials":true,"id":"Fuga nostrum velit hic excepturi omnis.","label":"Odio labore pariatur nobis.","login_customer_id":"Doloribus qui consequatur et dignissimos ex.","project_id":"Distinctio quos ut unde et atque.","status":"error","version":8118771688807322679}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-google-ads lfx-v2-campaign-service-connections","description":"Replace the Google Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGoogleAdsRequestBody"},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Dolor quia necessitatibus."},"example":"Excepturi iure voluptas voluptatem."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAdsConnection4"},"example":{"account_id":"Nobis soluta.","has_credentials":false,"id":"Vitae perspiciatis eveniet quibusdam nobis ut asperiores.","label":"Quis dicta voluptate aut in corporis.","login_customer_id":"Deleniti voluptas aperiam vel quae laudantium.","project_id":"Aut enim et.","status":"active","version":4385483960184879572}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-google-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-google-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-google-ads lfx-v2-campaign-service-connections","description":"Verify the stored Google Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-google-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Rerum itaque consequatur laboriosam non.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-hubspot lfx-v2-campaign-service-connections","description":"Soft-delete the project's HubSpot connection.","operationId":"lfx-v2-campaign-service-connections#delete-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-hubspot lfx-v2-campaign-service-connections","description":"Get the project's HubSpot connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Sunt odit molestiae ab praesentium enim."},"example":"Earum non fugiat placeat."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection3"},"example":{"account_id":"Molestiae suscipit non iusto.","brand_kit":"Et cupiditate molestiae ea ullam minima voluptatem.","has_credentials":false,"id":"Eaque ut laudantium nostrum ratione.","label":"Voluptate itaque fugit rerum cumque.","portal_id":"Soluta omnis eius dicta ut.","project_id":"Eius laborum.","sender_email":"Distinctio dolore velit pariatur.","sender_name":"Assumenda perspiciatis dolor porro.","status":"error","version":5347858386956008427}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-hubspot lfx-v2-campaign-service-connections","description":"Create the project's HubSpot connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHubspotRequestBody"},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."},"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ad a et inventore tempora facilis dolores."},"example":"Animi similique voluptatem vel voluptatibus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection2"},"example":{"account_id":"Esse voluptate eum laboriosam distinctio non.","brand_kit":"Harum eos eveniet ut.","has_credentials":false,"id":"Rem quia vel ipsam dolores.","label":"Ullam facilis ab sapiente voluptatem.","portal_id":"Id aut et adipisci quo ut.","project_id":"Et qui ut libero numquam.","sender_email":"Deserunt voluptates ut culpa saepe necessitatibus veritatis.","sender_name":"Enim id voluptas aperiam distinctio.","status":"inactive","version":8626906144120400240}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-hubspot lfx-v2-campaign-service-connections","description":"Replace the HubSpot connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHubspotRequestBody"},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Amet quam consequuntur nulla quaerat."},"example":"Commodi dolores autem doloribus laborum et."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HubspotConnection4"},"example":{"account_id":"Non unde.","brand_kit":"Incidunt velit provident perspiciatis.","has_credentials":true,"id":"Quia minus vero.","label":"Fugit alias nisi qui maiores.","portal_id":"Doloremque explicabo ut.","project_id":"Ut quis animi molestiae.","sender_email":"Odio odio sed minus quia quia ea.","sender_name":"Autem iste ea in ipsa.","status":"error","version":7175407383173057293}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-hubspot lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialHubspotRequestBody"},"example":{"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-hubspot/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-hubspot lfx-v2-campaign-service-connections","description":"Verify the stored HubSpot credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-hubspot","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Sunt itaque omnis ut ex sunt sit.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-linkedin-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's LinkedIn Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-linkedin-ads lfx-v2-campaign-service-connections","description":"Get the project's LinkedIn Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quo porro asperiores eum officiis."},"example":"Autem assumenda dolor qui ut facere."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection3"},"example":{"account_id":"Et eum est molestiae necessitatibus magni.","has_credentials":true,"id":"Voluptatibus deserunt.","label":"Et eligendi et numquam dolorum consequatur in.","org_id":"Deserunt ipsum dolorum quas error.","project_id":"Necessitatibus eos.","status":"error","version":3367774197321419764}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-linkedin-ads lfx-v2-campaign-service-connections","description":"Create the project's LinkedIn Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"},"credentials":{"access_token":"Illo minima eaque earum."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Est similique rerum consequatur."},"example":"Aut consectetur mollitia repellat."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection2"},"example":{"account_id":"Qui enim cumque saepe.","has_credentials":true,"id":"Eligendi tempore provident dicta temporibus debitis porro.","label":"Aperiam quis quis reprehenderit non quae.","org_id":"Accusantium aut.","project_id":"Odio incidunt distinctio sint.","status":"error","version":6613376414740649303}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkedinAdsRequestBody"},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Qui veniam architecto deleniti adipisci quas itaque."},"example":"Fuga rerum iure possimus et."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedinAdsConnection4"},"example":{"account_id":"Est rerum.","has_credentials":true,"id":"Et accusamus sed non sequi.","label":"Sunt est labore libero.","org_id":"Natus expedita aspernatur consequuntur quod ducimus molestias.","project_id":"Assumenda quam deleniti et et nam quia.","status":"error","version":313762477408274568}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-linkedin-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialLinkedinAdsRequestBody"},"example":{"credentials":{"access_token":"Illo minima eaque earum."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-linkedin-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-linkedin-ads lfx-v2-campaign-service-connections","description":"Verify the stored LinkedIn Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-linkedin-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Voluptatem iste nisi in aperiam.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-meta-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Meta Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-meta-ads lfx-v2-campaign-service-connections","description":"Get the project's Meta Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Et voluptatum excepturi nemo."},"example":"Veniam doloremque dolores voluptatum aut mollitia aperiam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection3"},"example":{"account_id":"Est distinctio sunt aliquid harum quis nam.","app_id":"Consectetur sint error ipsam maiores.","has_credentials":true,"id":"Facilis qui ducimus modi ut culpa.","label":"Eius error autem.","page_id":"Rerum illo mollitia odio et dolor.","project_id":"Blanditiis officiis quia quisquam corrupti distinctio.","status":"active","version":2737266573164142326}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-meta-ads lfx-v2-campaign-service-connections","description":"Create the project's Meta Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."},"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Hic voluptatibus accusamus vel."},"example":"Quo maxime laboriosam alias molestiae ea."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection2"},"example":{"account_id":"Sed numquam necessitatibus laudantium est ut.","app_id":"Voluptatem possimus et corrupti et nobis.","has_credentials":false,"id":"Nostrum corporis quasi magnam.","label":"Ipsa quo vel architecto quas eligendi impedit.","page_id":"Qui possimus.","project_id":"Qui quidem exercitationem.","status":"deleted","version":5650595339177390494}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-meta-ads lfx-v2-campaign-service-connections","description":"Replace the Meta Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMetaAdsRequestBody"},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Porro et fuga facere est qui vero."},"example":"Molestias quisquam est."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetaAdsConnection4"},"example":{"account_id":"Porro voluptate.","app_id":"Velit dolores omnis placeat unde distinctio ducimus.","has_credentials":false,"id":"Perspiciatis deleniti.","label":"Facilis suscipit.","page_id":"Eligendi quasi eos ipsa et dolore.","project_id":"Non sint inventore iste iure ut.","status":"error","version":7363633618316247458}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-meta-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialMetaAdsRequestBody"},"example":{"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-meta-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-meta-ads lfx-v2-campaign-service-connections","description":"Verify the stored Meta Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-meta-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Dignissimos non sed omnis officiis et.","ok":true}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-microsoft-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Microsoft Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-microsoft-ads lfx-v2-campaign-service-connections","description":"Get the project's Microsoft Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Soluta porro distinctio cupiditate."},"example":"Voluptatem optio perspiciatis reprehenderit."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection3"},"example":{"account_id":"Deserunt quod.","customer_id":"Nisi blanditiis eveniet quasi ea.","has_credentials":true,"id":"Perspiciatis porro.","label":"Rem error.","project_id":"Distinctio amet enim.","status":"active","version":3519020753859014023}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-microsoft-ads lfx-v2-campaign-service-connections","description":"Create the project's Microsoft Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."},"credentials":{"client_id":"Officiis aut fugiat optio ut.","client_secret":"Et voluptas tenetur nobis facere.","developer_token":"Blanditiis mollitia dolores illo nostrum magnam.","refresh_token":"Quo corporis recusandae dicta quibusdam."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Ipsum ullam quis est incidunt debitis qui."},"example":"Quia sunt dignissimos pariatur dolorem natus."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection2"},"example":{"account_id":"Eum et ipsum expedita reprehenderit eos.","customer_id":"Deleniti nostrum.","has_credentials":true,"id":"Omnis nobis.","label":"Rerum explicabo.","project_id":"Deserunt repellendus corrupti itaque quo sit.","status":"deleted","version":7665319414608673783}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the Microsoft Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMicrosoftAdsRequestBody"},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Necessitatibus quam."},"example":"Assumenda repellat et rerum cum."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MicrosoftAdsConnection4"},"example":{"account_id":"Labore omnis provident nihil aut rerum est.","customer_id":"Corporis sint non dolorem repellendus.","has_credentials":false,"id":"Vel voluptate necessitatibus.","label":"Mollitia aut rerum quis et modi.","project_id":"Et et itaque sunt.","status":"active","version":2920255620636300538}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-microsoft-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialGoogleAdsRequestBody"},"example":{"credentials":{"client_id":"Officiis aut fugiat optio ut.","client_secret":"Et voluptas tenetur nobis facere.","developer_token":"Blanditiis mollitia dolores illo nostrum magnam.","refresh_token":"Quo corporis recusandae dicta quibusdam."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-microsoft-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-microsoft-ads lfx-v2-campaign-service-connections","description":"Verify the stored Microsoft Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-microsoft-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Distinctio dolor aut assumenda fugiat quia.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-reddit-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's Reddit Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-reddit-ads lfx-v2-campaign-service-connections","description":"Get the project's Reddit Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Et et."},"example":"At qui error totam aut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection3"},"example":{"account_id":"Unde dicta autem dolorem qui explicabo.","has_credentials":true,"id":"Omnis neque autem ex eos.","label":"A necessitatibus molestiae.","project_id":"Eum et sint corrupti eos.","status":"deleted","version":4219814008454412160}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-reddit-ads lfx-v2-campaign-service-connections","description":"Create the project's Reddit Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."},"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quos atque dolorem error fugiat qui."},"example":"Enim molestiae suscipit sequi et."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection2"},"example":{"account_id":"Pariatur debitis.","has_credentials":true,"id":"Est dolor ut corporis.","label":"Illo vitae occaecati omnis.","project_id":"Cupiditate ut illo dolor quaerat totam.","status":"active","version":1381780689877724774}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the Reddit Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRedditAdsRequestBody"},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Veritatis culpa quasi qui iure consequuntur porro."},"example":"Nobis quas sunt dolorum."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedditAdsConnection4"},"example":{"account_id":"Perferendis velit atque.","has_credentials":true,"id":"Ut similique ea minima magnam non ut.","label":"Est dolores est et recusandae nihil.","project_id":"Iure nam.","status":"active","version":4591742417728159020}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-reddit-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialRedditAdsRequestBody"},"example":{"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-reddit-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-reddit-ads lfx-v2-campaign-service-connections","description":"Verify the stored Reddit Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-reddit-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Adipisci sint enim.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads":{"delete":{"tags":["lfx-v2-campaign-service-connections"],"summary":"delete-twitter-ads lfx-v2-campaign-service-connections","description":"Soft-delete the project's X/Twitter Ads connection.","operationId":"lfx-v2-campaign-service-connections#delete-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["lfx-v2-campaign-service-connections"],"summary":"get-twitter-ads lfx-v2-campaign-service-connections","description":"Get the project's X/Twitter Ads connection (credentials redacted; returns ETag).","operationId":"lfx-v2-campaign-service-connections#get-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Quasi vel sint nisi laborum laboriosam assumenda."},"example":"Ut totam quia ipsam quia ut quibusdam."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection3"},"example":{"account_id":"Magni consequatur dignissimos itaque quia.","funding_instrument_id":"Et voluptatem deserunt consequuntur omnis sed dolore.","has_credentials":true,"id":"Repellat assumenda nesciunt distinctio eaque aliquam.","label":"Et reprehenderit dignissimos.","project_id":"Veniam consectetur deserunt in hic dolorum facere.","status":"inactive","version":7072791787806691868}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"create-twitter-ads lfx-v2-campaign-service-connections","description":"Create the project's X/Twitter Ads connection (singleton; 409 if one already exists).","operationId":"lfx-v2-campaign-service-connections#create-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."},"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}}}}},"responses":{"201":{"description":"Created response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Eum aut delectus voluptatem."},"example":"Repudiandae sequi."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection2"},"example":{"account_id":"Illum illum dolores tenetur ut illum.","funding_instrument_id":"Delectus est aut et rerum in expedita.","has_credentials":true,"id":"Omnis consequatur.","label":"Voluptatum ducimus tempore quae eum consequatur dolore.","project_id":"Cum non atque aut autem sint.","status":"active","version":1365614189000530579}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"409":{"description":"Conflict: A connection already exists for this provider on the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["lfx-v2-campaign-service-connections"],"summary":"update-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials).","operationId":"lfx-v2-campaign-service-connections#update-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"If-Match","in":"header","description":"If-Match header carrying the current ETag/version","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header carrying the current ETag/version","example":"3"},"example":"3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTwitterAdsRequestBody"},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."}}}}},"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value (mirrors version)","schema":{"type":"string","description":"ETag header value (mirrors version)","example":"Delectus vel."},"example":"Iure doloremque vel et aut."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwitterAdsConnection4"},"example":{"account_id":"Quam officiis aut sit architecto ut unde.","funding_instrument_id":"Voluptates atque.","has_credentials":false,"id":"Sunt qui cupiditate ullam ab suscipit et.","label":"Assumenda dignissimos beatae vitae aut ea dolor.","project_id":"Harum corporis deleniti doloribus.","status":"active","version":7481335546350109928}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"412":{"description":"PreconditionFailed: ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionFailedError"},"example":{"code":"412","message":"The supplied ETag does not match the current version."}}}},"428":{"description":"PreconditionRequired: If-Match header required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreconditionRequiredError"},"example":{"code":"428","message":"An If-Match header is required."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/set-credential":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"set-credential-twitter-ads lfx-v2-campaign-service-connections","description":"Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream.","operationId":"lfx-v2-campaign-service-connections#set-credential-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCredentialTwitterAdsRequestBody"},"example":{"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/connection-twitter-ads/test":{"post":{"tags":["lfx-v2-campaign-service-connections"],"summary":"test-twitter-ads lfx-v2-campaign-service-connections","description":"Verify the stored X/Twitter Ads credential against the provider.","operationId":"lfx-v2-campaign-service-connections#test-twitter-ads","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionTestResult"},"example":{"message":"Voluptate reprehenderit.","ok":false}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/projects/{project_id}/jobs/{job_id}":{"get":{"tags":["lfx-v2-campaign-service-briefs"],"summary":"get-job lfx-v2-campaign-service-briefs","description":"Poll campaign-creation job status.","operationId":"lfx-v2-campaign-service-briefs#get-job","parameters":[{"name":"project_id","in":"path","description":"Project UUID or slug that scopes the connection","required":true,"schema":{"type":"string","description":"Project UUID or slug that scopes the connection","example":"cncf"},"example":"cncf"},{"name":"job_id","in":"path","description":"Job UUID","required":true,"schema":{"type":"string","description":"Job UUID","example":"35d610c9-3332-4801-9b65-11002ee56afa","format":"uuid"},"example":"a22e3d45-a3ff-4bb1-9983-d7ce7173fde7"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPollResponse"},"example":{"error":"Aut aut ducimus dolores.","job_id":"Accusantium enim temporibus dolor ipsam.","result":[{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."},{"campaign_id":"Quisquam voluptatibus sint quia.","error":"Tempore nemo illum ut maiores.","ok":false,"platform":"Officiis est."}],"status":"failed"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"code":"400","message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"code":"404","message":"The connection was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"code":"409","message":"A connection for this provider already exists on the project."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"code":"500","message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnServiceUnavailableError"},"example":{"code":"503","message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"400"},"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"code":"400","message":"The request was invalid."},"required":["code","message"]},"Brief":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Eligendi ipsam aut vel atque."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"In voluptas."},"event_details":{"description":"Extracted event/course details","example":"Rerum cum eaque nesciunt est totam inventore."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Exercitationem eligendi magni quidem."},"id":{"type":"string","description":"Brief UUID","example":"Amet quaerat modi."},"keywords":{"description":"Keyword list","example":"Reiciendis sit minima."},"platforms":{"type":"array","items":{"type":"string","example":"Perspiciatis alias et amet ex molestiae."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Libero et consequuntur suscipit distinctio pariatur mollitia.","Autem quo tempora voluptate fuga.","Distinctio maiores mollitia aut."]},"program_type":{"type":"string","description":"Funnel context","example":"events","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Eum totam voluptatem eos."},"status":{"type":"string","description":"Lifecycle status","example":"archived","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Unde et."},"url":{"type":"string","description":"Event/course page URL","example":"In quia."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1675828466631258091,"format":"int64"}},"example":{"copy":"Magni eos voluptatem sint hic ut.","etag":"Nisi repudiandae.","event_details":"Molestiae eius in reprehenderit nobis hic.","event_slug":"Est natus sequi beatae.","id":"Placeat dolores numquam molestias quibusdam aliquid.","keywords":"Harum aliquam sunt eum.","platforms":["Similique omnis.","Aliquid dignissimos minima aut fugit.","Et iure laudantium quia est molestiae.","Ipsum neque sapiente aliquid quidem velit."],"program_type":"events","project_id":"Fuga est aliquam debitis.","status":"draft","targeting":"Eaque occaecati.","url":"Aut omnis labore et quis consequatur dolores.","version":1167300153394787715},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief2":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Quo veritatis nisi velit distinctio animi."},"event_details":{"description":"Extracted event/course details","example":"Sapiente perspiciatis dolores dignissimos soluta non vel."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Aut mollitia pariatur perferendis placeat dolores."},"id":{"type":"string","description":"Brief UUID","example":"Sint nisi sunt sed."},"keywords":{"description":"Keyword list","example":"Provident rerum et voluptas tenetur esse."},"platforms":{"type":"array","items":{"type":"string","example":"Qui pariatur odio reprehenderit nam."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Et id sint suscipit quia ipsa voluptas.","Non neque optio.","Architecto molestiae cum.","Perspiciatis consequatur beatae sunt officiis sequi qui."]},"program_type":{"type":"string","description":"Funnel context","example":"membership","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Dolor eaque culpa quae eius omnis id."},"status":{"type":"string","description":"Lifecycle status","example":"approved","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Assumenda sed minima nihil."},"url":{"type":"string","description":"Event/course page URL","example":"Dolore voluptatem qui eveniet amet."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":9087350093442770114,"format":"int64"}},"example":{"copy":"Dolor cumque dolores.","event_details":"Ad et in voluptatem et perspiciatis.","event_slug":"Quia aut impedit a veritatis.","id":"Voluptatem vel dolor molestiae quia voluptatem.","keywords":"Aut ratione dicta.","platforms":["Qui voluptas veniam.","Voluptas maxime non illo ab in voluptatem.","Est magnam molestiae soluta."],"program_type":"education","project_id":"Libero qui.","status":"draft","targeting":"Aspernatur culpa sed non qui ad.","url":"Consequuntur sed ea sunt debitis.","version":1905073879386313651},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief3":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Alias quis magni ad doloremque."},"event_details":{"description":"Extracted event/course details","example":"Facere iste."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Reprehenderit saepe quas commodi qui qui mollitia."},"id":{"type":"string","description":"Brief UUID","example":"Facere rerum voluptatem saepe eligendi."},"keywords":{"description":"Keyword list","example":"Et quam totam."},"platforms":{"type":"array","items":{"type":"string","example":"Reiciendis numquam consequatur."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Qui eaque ut iste.","Aspernatur delectus rem."]},"program_type":{"type":"string","description":"Funnel context","example":"education","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Non minima possimus totam occaecati enim eaque."},"status":{"type":"string","description":"Lifecycle status","example":"archived","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Eos repudiandae voluptate est enim aut laborum."},"url":{"type":"string","description":"Event/course page URL","example":"Recusandae voluptates."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7978828505057037537,"format":"int64"}},"example":{"copy":"Quae qui optio magni itaque iure.","event_details":"Placeat dolores culpa sed.","event_slug":"Autem ullam.","id":"Est et beatae.","keywords":"Autem aperiam quis.","platforms":["Eveniet explicabo voluptatem et quis.","Ea ut voluptatem velit.","Aut est est eum ut ut.","Et molestiae blanditiis nihil dolor exercitationem."],"program_type":"events","project_id":"Est officiis enim qui.","status":"archived","targeting":"Enim odit numquam.","url":"Quis sed consequatur velit atque nam.","version":4992739119008200883},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief4":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Dolor eum et consequatur."},"event_details":{"description":"Extracted event/course details","example":"Quae adipisci doloribus ullam."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Dignissimos aut eaque."},"id":{"type":"string","description":"Brief UUID","example":"Dolores at voluptatem beatae quis dignissimos."},"keywords":{"description":"Keyword list","example":"Optio enim nisi doloremque rerum sed consequatur."},"platforms":{"type":"array","items":{"type":"string","example":"Fuga ducimus dolorem ut."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Ad optio magnam sequi esse.","Modi vel aut rerum.","Ad minus vero assumenda cum nemo.","Nobis assumenda aut dolorem ea veniam."]},"program_type":{"type":"string","description":"Funnel context","example":"events","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Et labore sit."},"status":{"type":"string","description":"Lifecycle status","example":"approved","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Quia amet vero dolorum incidunt officia nihil."},"url":{"type":"string","description":"Event/course page URL","example":"Autem ut unde dolore id a."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2033029701447525509,"format":"int64"}},"example":{"copy":"Corrupti sint molestiae occaecati est.","event_details":"Nam eius nulla suscipit ullam.","event_slug":"Sint consequuntur omnis distinctio possimus non aliquid.","id":"Perferendis tempore eum laboriosam.","keywords":"Labore est ut ratione nobis ut occaecati.","platforms":["Est et est.","Deleniti non in iure dolores et quis.","Recusandae similique assumenda ut quia.","Nesciunt minus natus non."],"program_type":"membership","project_id":"Odio at magnam saepe incidunt.","status":"approved","targeting":"Minima veritatis sed impedit laborum.","url":"Facilis neque sed alias dolore id.","version":7666233696583259840},"required":["id","project_id","program_type","event_slug","status","version"]},"Brief5":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Consequatur laborum."},"event_details":{"description":"Extracted event/course details","example":"Cum ipsa molestias voluptatibus nam debitis hic."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Et et iure tempora exercitationem voluptatum."},"id":{"type":"string","description":"Brief UUID","example":"Ut qui sed explicabo dolorem totam non."},"keywords":{"description":"Keyword list","example":"Beatae non et excepturi sit minus."},"platforms":{"type":"array","items":{"type":"string","example":"Ut corrupti sunt labore."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Cum ipsum quo possimus.","Quam culpa."]},"program_type":{"type":"string","description":"Funnel context","example":"events","enum":["events","education","membership"]},"project_id":{"type":"string","description":"Owning project","example":"Nesciunt sed magnam sed vero nulla."},"status":{"type":"string","description":"Lifecycle status","example":"archived","enum":["draft","approved","archived"]},"targeting":{"description":"Targeting recommendation","example":"Et quisquam dicta."},"url":{"type":"string","description":"Event/course page URL","example":"Autem et."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6074244296634719915,"format":"int64"}},"example":{"copy":"Adipisci vel.","event_details":"Et eum ex doloremque quia.","event_slug":"Ipsam consequatur qui illo totam.","id":"Est libero enim quo sit aut tempora.","keywords":"Eum itaque.","platforms":["Expedita commodi in dolores.","Similique eaque autem impedit tenetur voluptatibus.","Libero quia quam harum molestiae cupiditate."],"program_type":"membership","project_id":"Neque assumenda sed.","status":"draft","targeting":"Recusandae recusandae est et fuga veniam natus.","url":"Dignissimos in tempore quo consequatur sint qui.","version":3604003677758724955},"required":["id","project_id","program_type","event_slug","status","version"]},"BriefInput":{"type":"object","properties":{"copy":{"description":"Ad copy","example":"Praesentium et."},"event_details":{"description":"Extracted event/course details","example":"Dolor consectetur eos iste."},"event_slug":{"type":"string","description":"Event/course slug (unique within the project)","example":"Porro commodi odio sunt esse."},"keywords":{"description":"Keyword list","example":"Veniam iusto consequatur quia sed officia eligendi."},"platforms":{"type":"array","items":{"type":"string","example":"Itaque voluptatem est quis impedit."},"description":"Suggested default platforms (a planning hint; binding selection is on the campaign)","example":["Harum et consequatur eligendi eum necessitatibus.","Magnam ut.","Voluptate voluptates corrupti sapiente.","Dolorum repellat expedita ut recusandae dolor."]},"program_type":{"type":"string","description":"Funnel context","example":"events","enum":["events","education","membership"]},"targeting":{"description":"Targeting recommendation","example":"Qui impedit."},"url":{"type":"string","description":"Event/course page URL","example":"Incidunt eos dolores laborum sed molestias."}},"example":{"copy":"Animi sit esse ratione aliquid.","event_details":"Repudiandae tenetur aperiam saepe vel.","event_slug":"Alias minus reprehenderit dignissimos aut velit.","keywords":"Laborum est quasi dignissimos.","platforms":["Omnis doloribus.","Cum ut quo.","Necessitatibus id sint dicta assumenda omnis voluptatem.","Praesentium voluptatem blanditiis non totam temporibus."],"program_type":"events","targeting":"Velit quis harum recusandae est ipsum.","url":"At sint laborum veniam."},"required":["program_type","event_slug"]},"Campaign":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Qui mollitia et eum veniam."},"campaign_name":{"type":"string","description":"Campaign name","example":"Culpa sed minima iure."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Et tempora quaerat et eius."},"id":{"type":"string","description":"Campaign UUID","example":"Sint qui alias voluptatem ut."},"platform":{"type":"string","description":"Channel","example":"Aliquam eaque voluptatem omnis."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Cupiditate aut."},"project_id":{"type":"string","description":"Owning project","example":"Ut recusandae modi."},"status":{"type":"string","description":"Campaign status","example":"Enim occaecati beatae eveniet molestiae ad voluptatum."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1755263284665276006,"format":"int64"}},"example":{"brief_id":"Et asperiores.","campaign_name":"Voluptatem aut debitis vitae unde velit eos.","etag":"Et et itaque qui repudiandae magnam.","id":"Magni illum consequatur quaerat soluta.","platform":"Maiores reiciendis.","platform_campaign_id":"Dolor ipsam recusandae maxime nesciunt repellendus.","project_id":"Vitae autem in.","status":"Accusantium odit rerum commodi ea.","version":3660260905327304928},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"Campaign2":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Velit at omnis autem."},"campaign_name":{"type":"string","description":"Campaign name","example":"Laboriosam et."},"id":{"type":"string","description":"Campaign UUID","example":"Doloremque odio."},"platform":{"type":"string","description":"Channel","example":"Ratione est omnis voluptas praesentium."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Aspernatur voluptatum facilis."},"project_id":{"type":"string","description":"Owning project","example":"Ipsum expedita animi."},"status":{"type":"string","description":"Campaign status","example":"Sapiente alias et error nobis minima."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4893105039439918924,"format":"int64"}},"example":{"brief_id":"Tempora rem ab earum debitis rerum perspiciatis.","campaign_name":"Voluptas itaque quo soluta maxime temporibus earum.","id":"Dolorem impedit qui.","platform":"Pariatur quae amet officia.","platform_campaign_id":"Quam nostrum.","project_id":"Ipsa ut animi fuga.","status":"Aperiam et ab.","version":8291275966671335223},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"Campaign3":{"type":"object","properties":{"brief_id":{"type":"string","description":"Parent brief","example":"Aut est et."},"campaign_name":{"type":"string","description":"Campaign name","example":"Consequatur nemo molestiae modi ut non atque."},"id":{"type":"string","description":"Campaign UUID","example":"Vel quia cum quas."},"platform":{"type":"string","description":"Channel","example":"Debitis ea molestiae eum voluptatum."},"platform_campaign_id":{"type":"string","description":"ID returned by the ad platform","example":"Illo eveniet."},"project_id":{"type":"string","description":"Owning project","example":"Minus sit necessitatibus."},"status":{"type":"string","description":"Campaign status","example":"Sed commodi voluptatibus."},"version":{"type":"integer","description":"Optimistic-concurrency version","example":5825805897083964981,"format":"int64"}},"example":{"brief_id":"Perspiciatis optio.","campaign_name":"Excepturi cumque ullam esse.","id":"Est fugit ratione consectetur.","platform":"Quia doloribus omnis et excepturi fugit dolores.","platform_campaign_id":"Quia voluptates voluptates nisi nihil aut molestias.","project_id":"Sed fuga facilis asperiores enim nisi.","status":"Non autem voluptatem ut.","version":7716950937642575525},"required":["id","project_id","brief_id","platform","campaign_name","status","version"]},"CampaignCreateInput":{"type":"object","properties":{"config":{"description":"Per-platform campaign configuration","example":"Omnis dolorem est aut quia recusandae."},"platforms":{"type":"array","items":{"type":"string","example":"hubspot","enum":["google-ads","linkedin-ads","meta-ads","reddit-ads","twitter-ads","microsoft-ads","hubspot"]},"description":"Platforms to create campaigns on (binding selection)","example":["google-ads"],"minItems":1}},"example":{"config":"Totam tempora vitae.","platforms":["reddit-ads"]},"required":["platforms"]},"CampaignUpdateInput":{"type":"object","properties":{"campaign_name":{"type":"string","description":"Campaign name","example":"Ullam qui rerum numquam quo."},"config":{"description":"Campaign configuration snapshot","example":"Quaerat molestias."},"status":{"type":"string","description":"Campaign status","example":"A quia magnam culpa ipsam totam."}},"example":{"campaign_name":"Sequi aut veniam ratione.","config":"Laboriosam cumque mollitia.","status":"Perferendis omnis ad voluptas esse."},"required":["campaign_name","status"]},"ConflictError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"409"},"message":{"type":"string","description":"Error message","example":"A connection for this provider already exists on the project."}},"example":{"code":"409","message":"A connection for this provider already exists on the project."},"required":["code","message"]},"ConnServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"ConnectionTestResult":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable detail","example":"Et enim totam et minima."},"ok":{"type":"boolean","description":"Whether the credential authenticated against the provider","example":false}},"example":{"message":"Est tempora qui.","ok":false},"required":["ok"]},"CreateBriefRequestBody":{"type":"object","properties":{"brief":{"$ref":"#/components/schemas/BriefInput"}},"example":{"brief":{"copy":"Autem veniam.","event_details":"Vel autem voluptates.","event_slug":"Et est laboriosam.","keywords":"Enim unde omnis.","platforms":["Nam exercitationem consectetur sint.","Necessitatibus ex ea ullam corrupti.","Et omnis accusantium.","Iure modi asperiores ut."],"program_type":"education","targeting":"Quam sed rem quia consequuntur ut.","url":"Facere sint totam sapiente vitae numquam ut."}},"required":["brief"]},"CreateCampaignsRequestBody":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/CampaignCreateInput"}},"example":{"input":{"config":"Quia quos ut tempora consequuntur cumque repudiandae.","platforms":["microsoft-ads","microsoft-ads","linkedin-ads"]}},"required":["input"]},"CreateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}},"required":["config","credentials"]},"CreateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"},"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."},"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}},"required":["config","credentials"]},"CreateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"},"credentials":{"access_token":"Illo minima eaque earum."}},"required":["config","credentials"]},"CreateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."},"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}},"required":["config","credentials"]},"CreateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/MicrosoftAdsCredentials"}},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."},"credentials":{"client_id":"Officiis aut fugiat optio ut.","client_secret":"Et voluptas tenetur nobis facere.","developer_token":"Blanditiis mollitia dolores illo nostrum magnam.","refresh_token":"Quo corporis recusandae dicta quibusdam."}},"required":["config","credentials"]},"CreateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."},"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}},"required":["config","credentials"]},"CreateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"},"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."},"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}},"required":["config","credentials"]},"GoogleAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Voluptatem repellat expedita inventore."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"In doloremque autem maxime hic aliquam ratione."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Odit provident sed quo aperiam."},"label":{"type":"string","description":"Optional friendly name","example":"Delectus harum voluptates sit est corporis."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Reprehenderit amet hic est odio."},"project_id":{"type":"string","description":"Owning project","example":"Aspernatur nihil et incidunt dolorem dolor sed."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2066924184302318041,"format":"int64"}},"example":{"account_id":"Ullam ipsum.","etag":"Dolore sed.","has_credentials":false,"id":"Occaecati rerum reiciendis.","label":"Magnam voluptates molestiae libero autem quod.","login_customer_id":"Sunt accusantium totam quia.","project_id":"Esse mollitia et dolorum accusantium aspernatur quos.","status":"error","version":3252068377050447352},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"GoogleAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Corporis et iste impedit voluptas."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Non recusandae et qui ut exercitationem officia."},"label":{"type":"string","description":"Optional friendly name","example":"Unde quia ipsa sint optio nobis quae."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Accusamus provident omnis."},"project_id":{"type":"string","description":"Owning project","example":"Tempora ea laboriosam."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3989869738197030603,"format":"int64"}},"example":{"account_id":"Repellendus saepe voluptatem dolores.","has_credentials":false,"id":"Eos earum fuga.","label":"Reiciendis similique laborum tempora qui incidunt.","login_customer_id":"Voluptates soluta animi sit ut.","project_id":"Est facilis.","status":"deleted","version":3133980880975603750},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Earum temporibus ipsum necessitatibus ratione possimus eum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Voluptas incidunt eligendi consequatur."},"label":{"type":"string","description":"Optional friendly name","example":"Illo repellat et cum."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Consequatur consequatur."},"project_id":{"type":"string","description":"Owning project","example":"Labore eos et repudiandae odit omnis cupiditate."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2647723585259991483,"format":"int64"}},"example":{"account_id":"Quaerat iure ipsam.","has_credentials":false,"id":"Et vel molestiae quo.","label":"Et sed debitis.","login_customer_id":"Aut minima adipisci aut quia quisquam veniam.","project_id":"Placeat reprehenderit natus pariatur.","status":"active","version":7897706105846459095},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Molestiae cupiditate quis modi blanditiis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Facilis sit autem."},"label":{"type":"string","description":"Optional friendly name","example":"Accusantium expedita alias placeat incidunt sit perspiciatis."},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"Magni sit harum."},"project_id":{"type":"string","description":"Owning project","example":"Nihil qui doloremque nam magnam aliquid impedit."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7345260181453091454,"format":"int64"}},"example":{"account_id":"Repellendus adipisci sit et enim quasi ut.","has_credentials":true,"id":"Et distinctio dignissimos fugiat voluptatibus natus.","label":"Dolorem omnis aspernatur veritatis adipisci.","login_customer_id":"Amet sed ipsam enim esse.","project_id":"Commodi et nihil et.","status":"active","version":2256303273039083512},"required":["id","project_id","account_id","has_credentials","status","version"]},"GoogleAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Google Ads customer ID","example":"8666746580"},"label":{"type":"string","description":"Optional friendly name","example":"TLF Main"},"login_customer_id":{"type":"string","description":"Manager account used for API access","example":"9746983954"}},"example":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"},"required":["account_id"]},"GoogleAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"In praesentium cumque."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Pariatur odio molestias voluptatum."},"developer_token":{"type":"string","description":"Google Ads developer token","example":"Nulla perferendis possimus deserunt iure et."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Voluptas necessitatibus cumque non porro qui molestias."}},"description":"Google Ads OAuth credential set. Write-only; never returned.","example":{"client_id":"Qui omnis nesciunt qui consequuntur.","client_secret":"Fuga atque.","developer_token":"Sequi culpa.","refresh_token":"Ea quidem ducimus tempora ea molestias."},"required":["refresh_token","client_id","client_secret","developer_token"]},"HubspotConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Beatae totam."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Molestiae veniam exercitationem blanditiis ratione non."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Voluptate nam iste architecto."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Eos ex laborum et doloremque."},"label":{"type":"string","description":"Optional friendly name","example":"Quo est."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Velit odio temporibus aut numquam est."},"project_id":{"type":"string","description":"Owning project","example":"Non laudantium quis ab non sunt."},"sender_email":{"type":"string","description":"Default sender address","example":"Ut vitae dolor soluta beatae."},"sender_name":{"type":"string","description":"Default sender name","example":"Enim velit voluptates officiis tempora possimus rerum."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7472986980795124202,"format":"int64"}},"example":{"account_id":"Quos provident.","brand_kit":"Dolorem ea et quidem quibusdam officiis.","etag":"Officiis modi quae consequatur voluptatem et.","has_credentials":false,"id":"Sed dolorum cupiditate quia qui dicta.","label":"Totam et voluptates atque consequatur consequuntur voluptates.","portal_id":"Culpa veritatis ut adipisci minus.","project_id":"Commodi facere vel.","sender_email":"Vero eum ea perspiciatis rerum.","sender_name":"Incidunt deleniti.","status":"inactive","version":413153877019026133},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"HubspotConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ut illum laboriosam debitis aut."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Quisquam sit ut aliquid consectetur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Et eveniet sunt."},"label":{"type":"string","description":"Optional friendly name","example":"Similique inventore et voluptatum."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Nobis ipsam blanditiis commodi accusamus maxime."},"project_id":{"type":"string","description":"Owning project","example":"Veniam ut et sit."},"sender_email":{"type":"string","description":"Default sender address","example":"Quia sed eum ad sunt impedit sint."},"sender_name":{"type":"string","description":"Default sender name","example":"Iusto aspernatur fugit et facere eligendi."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3458229418861381091,"format":"int64"}},"example":{"account_id":"Fuga et id voluptatem et et et.","brand_kit":"Velit architecto error consequatur nam.","has_credentials":false,"id":"Harum nisi illo quia.","label":"Aliquam vel cum alias iusto.","portal_id":"Doloribus ex dolor quia et dolores.","project_id":"Aut in voluptate quam delectus optio saepe.","sender_email":"Distinctio ea.","sender_name":"Ab iste earum consequatur laboriosam maxime nemo.","status":"error","version":6330333226367628566},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Reiciendis voluptas."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Eligendi facere."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Ipsum est a."},"label":{"type":"string","description":"Optional friendly name","example":"Et corporis error eligendi voluptatem repellat consectetur."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Nostrum quod rerum dolorum."},"project_id":{"type":"string","description":"Owning project","example":"Quia voluptatem veritatis molestiae similique."},"sender_email":{"type":"string","description":"Default sender address","example":"Doloribus et ex enim rem modi omnis."},"sender_name":{"type":"string","description":"Default sender name","example":"Atque quo sit iure quos."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1162295855624220314,"format":"int64"}},"example":{"account_id":"Vitae sapiente dolor unde harum dolorem iusto.","brand_kit":"Quis inventore iste nemo rerum corporis repellat.","has_credentials":true,"id":"Quo eveniet fuga quaerat.","label":"Deserunt eligendi rerum architecto necessitatibus alias.","portal_id":"Qui repellendus sit incidunt in et.","project_id":"Et modi eveniet magni repellendus et.","sender_email":"Et nesciunt et culpa similique quasi ratione.","sender_name":"Laboriosam dolorem ut dolorem odio aut reprehenderit.","status":"active","version":7214923908250326040},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Et eveniet eaque non architecto aut."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Nihil odio facere magnam quasi dolorem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Excepturi iusto in."},"label":{"type":"string","description":"Optional friendly name","example":"Rem aliquam voluptatem."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Enim distinctio et corrupti magnam deleniti nam."},"project_id":{"type":"string","description":"Owning project","example":"Velit in."},"sender_email":{"type":"string","description":"Default sender address","example":"Esse temporibus eaque ut voluptatibus."},"sender_name":{"type":"string","description":"Default sender name","example":"Qui officia tempora alias quis."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4048071747606254476,"format":"int64"}},"example":{"account_id":"Voluptatum nemo adipisci.","brand_kit":"Optio enim repellendus commodi quia quod sit.","has_credentials":false,"id":"Sint consequatur quisquam nihil.","label":"Laboriosam placeat nesciunt tempore.","portal_id":"Molestias aliquid dolorum consequatur est qui qui.","project_id":"Accusamus illum in delectus magnam corporis velit.","sender_email":"Enim aut.","sender_name":"Dolores sed.","status":"deleted","version":1989293428795722898},"required":["id","project_id","account_id","has_credentials","status","version"]},"HubspotConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"HubSpot list/audience ID","example":"Unde perspiciatis."},"brand_kit":{"type":"string","description":"Per-project brand kit selector","example":"Assumenda quam possimus aut ipsa."},"label":{"type":"string","description":"Optional friendly name","example":"Sed qui dolor illo facere ut voluptatem."},"portal_id":{"type":"string","description":"HubSpot portal/account ID","example":"Rerum maiores in alias est laboriosam saepe."},"sender_email":{"type":"string","description":"Default sender address","example":"Perferendis placeat earum occaecati et."},"sender_name":{"type":"string","description":"Default sender name","example":"Eaque magni omnis dolorem."}},"example":{"account_id":"Et natus possimus cupiditate.","brand_kit":"Quod amet itaque aut.","label":"Quae et perspiciatis numquam.","portal_id":"Molestiae quia neque atque delectus.","sender_email":"Repudiandae eum.","sender_name":"Ut itaque voluptatem unde adipisci aut."},"required":["account_id"]},"HubspotCredentials":{"type":"object","properties":{"private_app_token":{"type":"string","description":"HubSpot private app token","example":"Aut rem molestiae assumenda ea."}},"description":"HubSpot private app token. Write-only; never returned.","example":{"private_app_token":"Nam repellat."},"required":["private_app_token"]},"InternalServerError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"500"},"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"code":"500","message":"An internal server error occurred."},"required":["code","message"]},"JobCreateResponse":{"type":"object","properties":{"job_id":{"type":"string","description":"Poll GET /projects/{projectId}/jobs/{jobId}","example":"Consequuntur esse culpa."},"platforms":{"type":"array","items":{"type":"string","example":"Ut ullam aliquam dolore iure consequatur."},"description":"Platforms this job will create on","example":["Iste nemo.","Sunt deleniti.","Natus quia labore non ut dolores et."]},"status":{"type":"string","description":"Initial status (always 'queued' on create)","example":"queued","enum":["queued"]}},"example":{"job_id":"Voluptatem ipsa.","platforms":["Ipsam tempora est.","Sequi ea ut vero consequatur possimus.","Assumenda sit delectus veniam."],"status":"queued"},"required":["job_id","status","platforms"]},"JobPollResponse":{"type":"object","properties":{"error":{"type":"string","description":"Terminal error, if any","example":"Temporibus illum impedit ullam eaque."},"job_id":{"type":"string","description":"Job UUID","example":"Sunt quis odio enim nam id voluptatem."},"result":{"type":"array","items":{"$ref":"#/components/schemas/PlatformResult"},"description":"Per-platform results, written once when the job reaches a terminal state","example":[{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."},{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."},{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."},{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."}]},"status":{"type":"string","description":"Job status","example":"queued","enum":["queued","running","succeeded","partial","failed"]}},"example":{"error":"Itaque optio consequatur.","job_id":"Harum sint neque quia.","result":[{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."},{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."},{"campaign_id":"Aliquam facilis iste sed voluptatum laboriosam.","error":"Vero voluptatum a quos a sed exercitationem.","ok":false,"platform":"Ullam enim voluptatem."}],"status":"failed"},"required":["job_id","status"]},"LinkedinAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Laboriosam quos omnis deserunt libero consequatur quisquam."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Modi cupiditate."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Soluta quod."},"label":{"type":"string","description":"Optional friendly name","example":"Omnis inventore voluptatum ab."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Ducimus qui modi consequatur."},"project_id":{"type":"string","description":"Owning project","example":"At fugiat."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7336458220235154606,"format":"int64"}},"example":{"account_id":"Aliquam et asperiores quae voluptatem velit.","etag":"Enim voluptas numquam.","has_credentials":true,"id":"Et et deleniti sint reiciendis.","label":"Hic quam impedit placeat consequatur.","org_id":"Et iusto eius dolor magnam necessitatibus.","project_id":"Eos et quidem est.","status":"error","version":5408757854107547795},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"LinkedinAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Adipisci omnis veritatis omnis praesentium omnis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Est laborum."},"label":{"type":"string","description":"Optional friendly name","example":"Quod aut sunt ex et autem delectus."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Aut natus aliquam."},"project_id":{"type":"string","description":"Owning project","example":"Libero necessitatibus."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6869928491943758950,"format":"int64"}},"example":{"account_id":"Tempore qui est.","has_credentials":true,"id":"Quas aliquid.","label":"Praesentium beatae quis ut eius.","org_id":"Excepturi provident saepe aliquam architecto.","project_id":"Saepe non tempore.","status":"active","version":8098284705619161180},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Minus pariatur praesentium amet."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Vero provident voluptas consequatur iusto."},"label":{"type":"string","description":"Optional friendly name","example":"Ut magnam aut illum commodi placeat sed."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Qui sint velit earum laboriosam dignissimos reiciendis."},"project_id":{"type":"string","description":"Owning project","example":"Pariatur quas."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1671194498896693162,"format":"int64"}},"example":{"account_id":"Provident autem repudiandae molestias est consectetur porro.","has_credentials":false,"id":"Quisquam voluptas quasi esse corporis molestiae.","label":"Dolorem totam voluptatem recusandae amet.","org_id":"Ea ut.","project_id":"Suscipit fugiat deserunt.","status":"inactive","version":7812167479041835646},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Et libero ea aut omnis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Doloremque aut eligendi perferendis aut et."},"label":{"type":"string","description":"Optional friendly name","example":"Id officiis architecto."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"Corrupti aut earum rerum ducimus dicta."},"project_id":{"type":"string","description":"Owning project","example":"Eum molestiae ea consectetur delectus."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6409712151005637179,"format":"int64"}},"example":{"account_id":"Et rerum repudiandae et at iusto.","has_credentials":false,"id":"Quo omnis et et molestiae dolorum.","label":"Consequatur iste eum amet vel impedit et.","org_id":"Veritatis saepe numquam rem sapiente.","project_id":"Perspiciatis a aut quas delectus a.","status":"inactive","version":8814877888903559420},"required":["id","project_id","account_id","has_credentials","status","version"]},"LinkedinAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"LinkedIn ad account ID","example":"538170226"},"label":{"type":"string","description":"Optional friendly name","example":"Est ipsam sint autem repudiandae asperiores et."},"org_id":{"type":"string","description":"LinkedIn organization URN id","example":"208777"}},"example":{"account_id":"538170226","label":"Quia consectetur non nulla dolore quibusdam ducimus.","org_id":"208777"},"required":["account_id","org_id"]},"LinkedinAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth access token","example":"Accusantium debitis adipisci qui iste explicabo."}},"description":"LinkedIn Ads credential. Write-only; never returned.","example":{"access_token":"Commodi qui."},"required":["access_token"]},"MetaAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ut magni beatae error deleniti."},"app_id":{"type":"string","description":"Meta app ID","example":"Enim non asperiores velit aut."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Culpa aut temporibus dolorem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Tenetur nobis qui."},"label":{"type":"string","description":"Optional friendly name","example":"Inventore consequatur velit autem eaque eaque."},"page_id":{"type":"string","description":"Facebook page ID","example":"Quis accusantium."},"project_id":{"type":"string","description":"Owning project","example":"Laboriosam ipsa voluptas et vel dolorum."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7112569565459851125,"format":"int64"}},"example":{"account_id":"Sit nihil enim sit.","app_id":"Ipsam eligendi est dolore.","etag":"Qui vitae iste.","has_credentials":false,"id":"Quod minima.","label":"Dolores dolores.","page_id":"Non ipsam optio in ratione accusamus quasi.","project_id":"Ipsum et et laudantium.","status":"deleted","version":6379674624138849400},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MetaAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ullam sunt rem non doloribus."},"app_id":{"type":"string","description":"Meta app ID","example":"Consequuntur quia et saepe aperiam."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Esse qui quisquam."},"label":{"type":"string","description":"Optional friendly name","example":"Officiis asperiores quibusdam accusamus possimus."},"page_id":{"type":"string","description":"Facebook page ID","example":"Corrupti ipsum."},"project_id":{"type":"string","description":"Owning project","example":"Est magni temporibus et molestias voluptatem molestias."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3056771457873327009,"format":"int64"}},"example":{"account_id":"Aut quidem beatae officiis expedita error.","app_id":"Corporis nulla magni.","has_credentials":false,"id":"Et illo aut natus.","label":"Dolorem labore laboriosam repellat harum.","page_id":"Debitis iusto molestias itaque.","project_id":"Accusamus ipsa qui.","status":"error","version":5243837876383249590},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Consequatur provident ea sint."},"app_id":{"type":"string","description":"Meta app ID","example":"Minus id deleniti."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fugit et soluta."},"label":{"type":"string","description":"Optional friendly name","example":"Voluptatem iste nemo numquam unde perspiciatis."},"page_id":{"type":"string","description":"Facebook page ID","example":"Facere ut repudiandae eum quibusdam."},"project_id":{"type":"string","description":"Owning project","example":"Et qui officiis repudiandae qui iste accusantium."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1953847576666852892,"format":"int64"}},"example":{"account_id":"Eum autem quia quis soluta nostrum cumque.","app_id":"Saepe amet voluptatem veniam.","has_credentials":false,"id":"Dolorem quia.","label":"Adipisci consectetur ut modi quo et.","page_id":"Rem autem quia velit aliquid.","project_id":"Non id.","status":"error","version":7788869661064623160},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Eos fugiat rem minima ab ea animi."},"app_id":{"type":"string","description":"Meta app ID","example":"Voluptatem libero consectetur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Vero possimus rem maiores voluptas voluptate."},"label":{"type":"string","description":"Optional friendly name","example":"Eos qui facilis possimus consequatur sequi."},"page_id":{"type":"string","description":"Facebook page ID","example":"Facere ea incidunt et molestiae."},"project_id":{"type":"string","description":"Owning project","example":"Quam quos dolor id debitis."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6530264541662515284,"format":"int64"}},"example":{"account_id":"Minus alias laudantium.","app_id":"Rerum numquam et ex aut cupiditate qui.","has_credentials":true,"id":"Ducimus temporibus ullam sint aliquam ea voluptatibus.","label":"Temporibus accusamus rerum.","page_id":"Eius reiciendis rerum voluptatem.","project_id":"Impedit incidunt sint.","status":"inactive","version":93554437328757363},"required":["id","project_id","account_id","has_credentials","status","version"]},"MetaAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Meta ad account ID","example":"act_193556282970417"},"app_id":{"type":"string","description":"Meta app ID","example":"Explicabo totam officia dolores."},"label":{"type":"string","description":"Optional friendly name","example":"Est et corporis maxime voluptate."},"page_id":{"type":"string","description":"Facebook page ID","example":"Reprehenderit consectetur accusamus eaque inventore et."}},"example":{"account_id":"act_193556282970417","app_id":"Accusantium aut reiciendis in.","label":"Reprehenderit aut ex dolor sint.","page_id":"Rem nihil vitae qui explicabo."},"required":["account_id"]},"MetaAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"Meta access token","example":"Mollitia earum aut qui sit rerum."},"app_secret":{"type":"string","description":"Meta app secret","example":"Tempora voluptatem rem fugit accusantium est necessitatibus."}},"description":"Meta Ads credential. Write-only; never returned.","example":{"access_token":"Officia dolorem.","app_secret":"Asperiores autem aut aut soluta quae."},"required":["access_token","app_secret"]},"MicrosoftAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"In voluptate debitis earum voluptates repellat."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Dolore expedita."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Dolorem tempore qui."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Necessitatibus quia facere vitae aperiam velit corrupti."},"label":{"type":"string","description":"Optional friendly name","example":"Deserunt minima quis impedit."},"project_id":{"type":"string","description":"Owning project","example":"Pariatur totam."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":3340961523623056274,"format":"int64"}},"example":{"account_id":"Molestias reprehenderit.","customer_id":"Ea pariatur.","etag":"Dolorem similique sapiente.","has_credentials":true,"id":"Sit omnis.","label":"Accusantium est.","project_id":"Porro saepe eius earum quo porro.","status":"active","version":5013574793121618962},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"MicrosoftAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Non rerum."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Tenetur magnam quaerat laborum voluptatibus accusamus itaque."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Culpa est ex."},"label":{"type":"string","description":"Optional friendly name","example":"Eius eos sit quam fugiat."},"project_id":{"type":"string","description":"Owning project","example":"Aliquid porro deserunt nostrum sed sint voluptas."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":396103720096053742,"format":"int64"}},"example":{"account_id":"Cumque adipisci.","customer_id":"Error incidunt consequuntur voluptas ullam optio.","has_credentials":true,"id":"Aut et rem eos quia.","label":"Laboriosam placeat.","project_id":"Modi accusantium et repudiandae facere dolorum.","status":"error","version":316921975293270952},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Reprehenderit nemo voluptatibus."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Tempore aut voluptas ipsa quis animi."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Eaque eos veniam quidem maiores adipisci."},"label":{"type":"string","description":"Optional friendly name","example":"Molestias ullam aut odit repellat nihil at."},"project_id":{"type":"string","description":"Owning project","example":"Impedit a."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1334308724833154060,"format":"int64"}},"example":{"account_id":"Ipsum et illum.","customer_id":"Enim est sapiente.","has_credentials":true,"id":"Sunt reprehenderit aliquam perspiciatis libero.","label":"Et qui quos corrupti quam unde.","project_id":"Ratione pariatur.","status":"inactive","version":4456369783861517132},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Quis ut in voluptatem nesciunt."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Unde adipisci rerum eius vitae perferendis."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Minus aut ut non."},"label":{"type":"string","description":"Optional friendly name","example":"Blanditiis est et saepe."},"project_id":{"type":"string","description":"Owning project","example":"Repellendus voluptatem."},"status":{"type":"string","description":"Connection status","example":"error","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":4034090178991751772,"format":"int64"}},"example":{"account_id":"Eum nemo architecto est.","customer_id":"Consequatur provident veniam omnis quia.","has_credentials":true,"id":"Quidem eum animi.","label":"Est error maiores excepturi exercitationem quo porro.","project_id":"Explicabo et quo et velit qui doloremque.","status":"deleted","version":5942764375177239504},"required":["id","project_id","account_id","has_credentials","status","version"]},"MicrosoftAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Microsoft Advertising account ID","example":"Laudantium eligendi ut et."},"customer_id":{"type":"string","description":"Microsoft Advertising customer ID","example":"Maiores nam sequi iste iusto reiciendis ipsa."},"label":{"type":"string","description":"Optional friendly name","example":"Est dolor velit et voluptatem mollitia placeat."}},"example":{"account_id":"Temporibus in qui provident corrupti ut similique.","customer_id":"Voluptas doloremque nobis consectetur voluptas voluptas.","label":"Saepe beatae."},"required":["account_id"]},"MicrosoftAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Beatae facilis dolor quod nihil quia."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Sed voluptas fuga officia nulla."},"developer_token":{"type":"string","description":"Microsoft Advertising developer token","example":"Non consequuntur qui qui impedit assumenda voluptas."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Recusandae consequatur nulla."}},"description":"Microsoft Advertising OAuth credential set. Write-only; never returned.","example":{"client_id":"Consequuntur provident nostrum vel ut.","client_secret":"Et nemo dicta illum omnis.","developer_token":"Nemo cum delectus laudantium.","refresh_token":"Dolor et aliquam deleniti."},"required":["client_id","client_secret","refresh_token","developer_token"]},"NotFoundError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"404"},"message":{"type":"string","description":"Error message","example":"The connection was not found."}},"example":{"code":"404","message":"The connection was not found."},"required":["code","message"]},"PlatformResult":{"type":"object","properties":{"campaign_id":{"type":"string","description":"Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost.","example":"Provident illo non quisquam iure."},"error":{"type":"string","description":"Failure reason (present when not ok)","example":"Est ut aliquid."},"ok":{"type":"boolean","description":"Whether the campaign was created (or reused) successfully","example":true},"platform":{"type":"string","description":"Platform this result is for","example":"Est et magni."}},"example":{"campaign_id":"Exercitationem accusamus pariatur saepe ab qui.","error":"Ab laboriosam sunt quis omnis.","ok":false,"platform":"Repellendus eveniet veritatis impedit aut iste earum."},"required":["platform","ok"]},"PreconditionFailedError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"412"},"message":{"type":"string","description":"Error message","example":"The supplied ETag does not match the current version."}},"example":{"code":"412","message":"The supplied ETag does not match the current version."},"required":["code","message"]},"PreconditionRequiredError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"428"},"message":{"type":"string","description":"Error message","example":"An If-Match header is required."}},"example":{"code":"428","message":"An If-Match header is required."},"required":["code","message"]},"RedditAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Sit soluta quia qui."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Expedita sed velit distinctio."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Optio quia similique vero est impedit quo."},"label":{"type":"string","description":"Optional friendly name","example":"Libero officia et quia."},"project_id":{"type":"string","description":"Owning project","example":"Nihil at."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1438953144952054594,"format":"int64"}},"example":{"account_id":"Optio rerum vel molestiae id.","etag":"Reiciendis excepturi.","has_credentials":false,"id":"Necessitatibus eos rerum.","label":"Qui quas aut provident suscipit.","project_id":"Veniam amet et quisquam voluptatem fuga.","status":"active","version":8349066598252210000},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"RedditAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Repellendus velit soluta est quidem in dolorem."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Omnis vitae."},"label":{"type":"string","description":"Optional friendly name","example":"Amet et vel ipsa ad doloremque qui."},"project_id":{"type":"string","description":"Owning project","example":"Voluptatem eum consequuntur."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7523183128313694126,"format":"int64"}},"example":{"account_id":"Autem repellat et.","has_credentials":true,"id":"Accusamus eligendi itaque est est.","label":"Nesciunt consequatur commodi.","project_id":"Explicabo doloremque possimus soluta cupiditate ab.","status":"active","version":7930594330395664596},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"At ratione unde dicta."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Praesentium omnis amet quia quia ea amet."},"label":{"type":"string","description":"Optional friendly name","example":"At explicabo reiciendis."},"project_id":{"type":"string","description":"Owning project","example":"Officia rerum eligendi ex."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":6548567568963242840,"format":"int64"}},"example":{"account_id":"Aut esse sint earum magnam.","has_credentials":true,"id":"Ipsum pariatur explicabo.","label":"Et perferendis aut voluptates voluptatem.","project_id":"Qui voluptas.","status":"inactive","version":7316195578965090635},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Corporis nemo et consectetur consequatur."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Fuga natus."},"label":{"type":"string","description":"Optional friendly name","example":"Eum voluptatem eveniet."},"project_id":{"type":"string","description":"Owning project","example":"Est sed et ducimus qui quas."},"status":{"type":"string","description":"Connection status","example":"active","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":8379899225513618277,"format":"int64"}},"example":{"account_id":"Tempore ut.","has_credentials":false,"id":"Consectetur repudiandae hic nostrum.","label":"Doloremque ad.","project_id":"Sed est architecto sit est est labore.","status":"active","version":477213815092233328},"required":["id","project_id","account_id","has_credentials","status","version"]},"RedditAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"Reddit advertiser ID","example":"t2_gv9wtbfa"},"label":{"type":"string","description":"Optional friendly name","example":"Dolores earum."}},"example":{"account_id":"t2_gv9wtbfa","label":"Est consequatur rerum neque velit nulla modi."},"required":["account_id"]},"RedditAdsCredentials":{"type":"object","properties":{"client_id":{"type":"string","description":"OAuth client id","example":"Consequatur aut vero."},"client_secret":{"type":"string","description":"OAuth client secret","example":"Excepturi natus est est saepe doloribus unde."},"refresh_token":{"type":"string","description":"OAuth refresh token","example":"Aut perspiciatis."}},"description":"Reddit Ads OAuth credential. Write-only; never returned.","example":{"client_id":"Adipisci ut.","client_secret":"Sit totam ut et.","refresh_token":"Quibusdam ea nam aut."},"required":["client_id","client_secret","refresh_token"]},"ServiceUnavailableError":{"type":"object","properties":{"code":{"type":"string","description":"HTTP status code","example":"503"},"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"code":"503","message":"The service is unavailable."},"required":["code","message"]},"SetCredentialGoogleAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/GoogleAdsCredentials"}},"example":{"credentials":{"client_id":"Architecto itaque doloribus voluptatem consequatur quis fugiat.","client_secret":"Recusandae qui culpa.","developer_token":"Accusamus et atque enim.","refresh_token":"Dolorum minus voluptatem."}},"required":["credentials"]},"SetCredentialHubspotRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/HubspotCredentials"}},"example":{"credentials":{"private_app_token":"Vitae vel omnis autem accusamus mollitia."}},"required":["credentials"]},"SetCredentialLinkedinAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/LinkedinAdsCredentials"}},"example":{"credentials":{"access_token":"Illo minima eaque earum."}},"required":["credentials"]},"SetCredentialMetaAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/MetaAdsCredentials"}},"example":{"credentials":{"access_token":"Enim ea.","app_secret":"Quidem molestiae deleniti omnis."}},"required":["credentials"]},"SetCredentialRedditAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/RedditAdsCredentials"}},"example":{"credentials":{"client_id":"Magni repudiandae.","client_secret":"Nihil corrupti repellat eius.","refresh_token":"Qui nobis et delectus quam."}},"required":["credentials"]},"SetCredentialTwitterAdsRequestBody":{"type":"object","properties":{"credentials":{"$ref":"#/components/schemas/TwitterAdsCredentials"}},"example":{"credentials":{"access_token":"Deleniti placeat fuga aut saepe.","access_token_secret":"Reiciendis pariatur repellat.","consumer_key":"Nulla dolorum deserunt ut sit dolor.","consumer_secret":"Eos nihil ipsam ex odio totam."}},"required":["credentials"]},"TwitterAdsConnection":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Ut quas officiis mollitia ducimus sunt repellendus."},"etag":{"type":"string","description":"ETag header value (mirrors version)","example":"Aliquam est autem vel eos minima."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Quas est deserunt maiores consequatur eum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Odit illo ut ut."},"label":{"type":"string","description":"Optional friendly name","example":"Debitis atque laboriosam unde facilis."},"project_id":{"type":"string","description":"Owning project","example":"Nam modi nostrum assumenda alias sed."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":2463177480955435124,"format":"int64"}},"example":{"account_id":"Quisquam recusandae nesciunt et.","etag":"Ut ea sequi quos qui quo et.","funding_instrument_id":"Perferendis et.","has_credentials":true,"id":"Voluptatum pariatur non ratione quo ullam.","label":"Facilis dolor consectetur sed.","project_id":"Occaecati hic ut magnam facilis.","status":"deleted","version":8315950268383046552},"required":["id","project_id","account_id","has_credentials","status","version","etag"]},"TwitterAdsConnection2":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Illum natus doloremque facere quis et beatae."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Magni facere saepe dignissimos eos facere."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Dolor sint consectetur ipsa nam ipsa magnam."},"label":{"type":"string","description":"Optional friendly name","example":"Sed est recusandae enim quidem."},"project_id":{"type":"string","description":"Owning project","example":"Eos cum."},"status":{"type":"string","description":"Connection status","example":"inactive","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1050609458141446118,"format":"int64"}},"example":{"account_id":"Quia sed harum.","funding_instrument_id":"Ut sint laborum est quia ducimus inventore.","has_credentials":false,"id":"Animi dolor ex saepe ipsa ipsam.","label":"Nisi consequatur non quam suscipit ea ea.","project_id":"Voluptas est est accusamus.","status":"inactive","version":7559450314363992253},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection3":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Eaque omnis nihil itaque."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Sunt distinctio consequuntur enim in."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":true},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Delectus soluta repudiandae et."},"label":{"type":"string","description":"Optional friendly name","example":"Sequi occaecati."},"project_id":{"type":"string","description":"Owning project","example":"Exercitationem cumque libero."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":7986908834898485319,"format":"int64"}},"example":{"account_id":"Voluptates cumque.","funding_instrument_id":"Enim corrupti.","has_credentials":false,"id":"Est recusandae amet beatae.","label":"Sapiente quam deserunt.","project_id":"Ut amet consequatur expedita nihil molestiae soluta.","status":"inactive","version":3218820863122651463},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnection4":{"type":"object","properties":{"account_id":{"type":"string","description":"Provider account identifier","example":"Rerum nesciunt veritatis consequatur porro."},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"Ratione provident laborum."},"has_credentials":{"type":"boolean","description":"Whether an encrypted credential is stored","example":false},"id":{"type":"string","description":"Service-generated connection UUID (not used in paths)","example":"Qui voluptas culpa."},"label":{"type":"string","description":"Optional friendly name","example":"Delectus non aut ipsum."},"project_id":{"type":"string","description":"Owning project","example":"Iste nihil itaque et."},"status":{"type":"string","description":"Connection status","example":"deleted","enum":["active","inactive","error","deleted"]},"version":{"type":"integer","description":"Optimistic-concurrency version","example":1595767401527646160,"format":"int64"}},"example":{"account_id":"Aut sed consequatur officiis et error.","funding_instrument_id":"Commodi occaecati.","has_credentials":false,"id":"Sed voluptatem repudiandae soluta.","label":"Nemo aut et qui.","project_id":"Et quisquam dolores.","status":"active","version":8795877030736776782},"required":["id","project_id","account_id","has_credentials","status","version"]},"TwitterAdsConnectionConfig":{"type":"object","properties":{"account_id":{"type":"string","description":"X/Twitter Ads account ID","example":"8r7gb"},"funding_instrument_id":{"type":"string","description":"Funding instrument for the ad account","example":"In iure molestiae quisquam."},"label":{"type":"string","description":"Optional friendly name","example":"Accusamus eos neque velit."}},"example":{"account_id":"8r7gb","funding_instrument_id":"Numquam voluptatem maiores ipsam dicta cum odit.","label":"Ipsam aperiam et totam in."},"required":["account_id"]},"TwitterAdsCredentials":{"type":"object","properties":{"access_token":{"type":"string","description":"OAuth 1.0a access token","example":"Iure aut tenetur ipsum ea ad ducimus."},"access_token_secret":{"type":"string","description":"OAuth 1.0a access token secret","example":"Aut temporibus."},"consumer_key":{"type":"string","description":"OAuth 1.0a consumer key","example":"Aut tenetur."},"consumer_secret":{"type":"string","description":"OAuth 1.0a consumer secret","example":"Et et."}},"description":"X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned.","example":{"access_token":"Necessitatibus non eum ipsum eveniet quam.","access_token_secret":"Aut amet aut voluptatum magnam.","consumer_key":"Laborum ipsum accusantium voluptatem libero.","consumer_secret":"Eligendi non."},"required":["consumer_key","consumer_secret","access_token","access_token_secret"]},"UpdateCampaignRequestBody":{"type":"object","properties":{"campaign":{"$ref":"#/components/schemas/CampaignUpdateInput"}},"example":{"campaign":{"campaign_name":"Eos doloribus autem corrupti sed voluptatum laborum.","config":"Perferendis qui maiores aut reiciendis.","status":"Suscipit quas sunt eos nulla."}},"required":["campaign"]},"UpdateGoogleAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/GoogleAdsConnectionConfig"}},"example":{"config":{"account_id":"8666746580","label":"TLF Main","login_customer_id":"9746983954"}},"required":["config"]},"UpdateHubspotRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/HubspotConnectionConfig"}},"example":{"config":{"account_id":"Maxime dicta qui aut.","brand_kit":"Corrupti voluptas.","label":"Dolor porro inventore alias error.","portal_id":"Suscipit eos nam ipsa.","sender_email":"Velit et impedit facilis quaerat.","sender_name":"Dolor ut velit quam omnis et dolor."}},"required":["config"]},"UpdateLinkedinAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/LinkedinAdsConnectionConfig"}},"example":{"config":{"account_id":"538170226","label":"Temporibus et provident maiores neque.","org_id":"208777"}},"required":["config"]},"UpdateMetaAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MetaAdsConnectionConfig"}},"example":{"config":{"account_id":"act_193556282970417","app_id":"Qui rerum est sed sed.","label":"Quis quam dolor occaecati earum iusto rerum.","page_id":"Cupiditate nobis."}},"required":["config"]},"UpdateMicrosoftAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MicrosoftAdsConnectionConfig"}},"example":{"config":{"account_id":"Ut necessitatibus dolor nemo.","customer_id":"Nisi numquam nihil id unde.","label":"Voluptatem sed illo numquam praesentium tenetur quas."}},"required":["config"]},"UpdateRedditAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/RedditAdsConnectionConfig"}},"example":{"config":{"account_id":"t2_gv9wtbfa","label":"Laboriosam qui id enim."}},"required":["config"]},"UpdateTwitterAdsRequestBody":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/TwitterAdsConnectionConfig"}},"example":{"config":{"account_id":"8r7gb","funding_instrument_id":"Dolorem non laudantium sunt minima repudiandae illo.","label":"Labore esse."}},"required":["config"]}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"JWT issued by Heimdall; audience is this service.","scheme":"bearer"}}},"tags":[{"name":"lfx-v2-campaign-service-svc","description":"The campaign service supports Marketing Operations campaign creation and management."},{"name":"lfx-v2-campaign-service-briefs","description":"Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation."},{"name":"lfx-v2-campaign-service-connections","description":"Manage a project's singleton, per-provider ad-platform connections."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index e05fa2db..0b2eccf2 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -7,13 +7,13 @@ servers: - url: http://localhost:80 description: Default server for lfx-v2-campaign-service paths: - /projects/{project_id}/connection-google-ads: - delete: + /projects/{project_id}/briefs: + post: tags: - - lfx-v2-campaign-service-connections - summary: delete-google-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's Google Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-google-ads + - lfx-v2-campaign-service-briefs + summary: create-brief lfx-v2-campaign-service-briefs + description: Create a brief. + operationId: lfx-v2-campaign-service-briefs#create-brief parameters: - name: project_id in: path @@ -24,9 +24,66 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBriefRequestBody' + example: + brief: + copy: Autem veniam. + event_details: Vel autem voluptates. + event_slug: Et est laboriosam. + keywords: Enim unde omnis. + platforms: + - Nam exercitationem consectetur sint. + - Necessitatibus ex ea ullam corrupti. + - Et omnis accusantium. + - Iure modi asperiores ut. + program_type: education + targeting: Quam sed rem quia consequuntur ut. + url: Facere sint totam sapiente vitae numquam ut. responses: - "204": - description: No Content response. + "201": + description: Created response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Hic ea et. + example: Et et commodi autem. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief2' + example: + copy: Aperiam eum. + event_details: Beatae soluta molestiae nihil quia omnis. + event_slug: Fuga recusandae odio nulla ut laboriosam. + id: Dolor molestias ipsa dolorum est. + keywords: Quam et voluptatibus illum dolorem. + platforms: + - Suscipit corrupti reiciendis et. + - Saepe consequatur. + - Sequi et. + program_type: education + project_id: Blanditiis quidem qui iusto sed. + status: approved + targeting: Eum dolorum. + url: Rerum delectus velit. + version: 6878204844566876538 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. "404": description: 'NotFound: Resource not found' content: @@ -36,6 +93,15 @@ paths: example: code: "404" message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. "500": description: 'InternalServerError: Internal server error' content: @@ -56,12 +122,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - get: + /projects/{project_id}/briefs/{brief_id}: + delete: tags: - - lfx-v2-campaign-service-connections - summary: get-google-ads lfx-v2-campaign-service-connections - description: Get the project's Google Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-google-ads + - lfx-v2-campaign-service-briefs + summary: delete-brief lfx-v2-campaign-service-briefs + description: Archive a brief (soft delete). + operationId: lfx-v2-campaign-service-briefs#delete-brief parameters: - name: project_id in: path @@ -72,30 +139,28 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 9bc01b99-89ba-4efb-8538-2b1612fe241d + format: uuid + example: 4185e754-4b3e-4607-95ac-0588f14c2d07 responses: - "200": - description: OK response. - headers: - ETag: - description: ETag header value (mirrors version) - schema: - type: string - description: ETag header value (mirrors version) - example: Inventore ratione id vel iusto optio repellendus. - example: Rerum eveniet nam sunt. + "204": + description: No Content response. + "400": + description: 'BadRequest: Bad request' content: application/json: schema: - $ref: '#/components/schemas/GoogleAdsConnection3' + $ref: '#/components/schemas/BadRequestError' example: - account_id: Vitae qui. - has_credentials: true - id: Minima autem libero natus id esse. - label: Odit eum commodi autem expedita dolorem. - login_customer_id: In laborum vel. - project_id: Sed adipisci eveniet et aut. - status: active - version: 4120562596096857294 + code: "400" + message: The request was invalid. "404": description: 'NotFound: Resource not found' content: @@ -105,6 +170,15 @@ paths: example: code: "404" message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. "500": description: 'InternalServerError: Internal server error' content: @@ -125,12 +199,12 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - post: + get: tags: - - lfx-v2-campaign-service-connections - summary: create-google-ads lfx-v2-campaign-service-connections - description: Create the project's Google Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-google-ads + - lfx-v2-campaign-service-briefs + summary: get-brief lfx-v2-campaign-service-briefs + description: Get a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-brief parameters: - name: project_id in: path @@ -141,46 +215,47 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateGoogleAdsRequestBody' - example: - config: - account_id: "8666746580" - label: TLF Main - login_customer_id: "9746983954" - credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: e3374dee-25a0-47a6-894e-2c8dacc2c71c + format: uuid + example: 9717fe0e-7c86-4c49-90fb-b9d2643146d7 responses: - "201": - description: Created response. + "200": + description: OK response. headers: ETag: description: ETag header value (mirrors version) schema: type: string description: ETag header value (mirrors version) - example: Occaecati quaerat eum quia a excepturi. - example: Inventore aut molestiae voluptas hic. - content: - application/json: - schema: - $ref: '#/components/schemas/GoogleAdsConnection2' - example: - account_id: Corrupti reprehenderit et non totam perspiciatis. - has_credentials: false - id: Harum laborum est adipisci sint. - label: Ipsa ex. - login_customer_id: Perspiciatis voluptatibus architecto velit. - project_id: Est impedit et tempore provident. - status: error - version: 6280538112072962831 + example: Dolores in. + example: Velit eligendi omnis est vel est et. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief3' + example: + copy: Voluptatem voluptas omnis est tempora ipsum aut. + event_details: Alias non sequi consequatur dolorum. + event_slug: Et nihil. + id: Et placeat. + keywords: Harum et minus voluptas. + platforms: + - Voluptas culpa. + - Quia tenetur ducimus. + - Facere harum enim consequatur id voluptatem. + program_type: membership + project_id: Qui esse. + status: draft + targeting: Debitis labore aut. + url: Sequi quis. + version: 1292213403290328942 "400": description: 'BadRequest: Bad request' content: @@ -190,8 +265,17 @@ paths: example: code: "400" message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. "409": - description: 'Conflict: A connection already exists for this provider on the project' + description: 'Conflict: Conflict' content: application/json: schema: @@ -221,10 +305,10 @@ paths: - jwt_header_Authorization: [] put: tags: - - lfx-v2-campaign-service-connections - summary: update-google-ads lfx-v2-campaign-service-connections - description: Replace the Google Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-google-ads + - lfx-v2-campaign-service-briefs + summary: update-brief lfx-v2-campaign-service-briefs + description: Replace a brief (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-brief parameters: - name: project_id in: path @@ -235,6 +319,16 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: cc32c8e5-5ca3-4bd4-b68b-42bdfc7b7724 + format: uuid + example: 9f0f6183-8826-466a-a574-30f45cedcda5 - name: If-Match in: header description: If-Match header carrying the current ETag/version @@ -249,12 +343,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateGoogleAdsRequestBody' + $ref: '#/components/schemas/CreateBriefRequestBody' example: - config: - account_id: "8666746580" - label: TLF Main - login_customer_id: "9746983954" + brief: + copy: Autem veniam. + event_details: Vel autem voluptates. + event_slug: Et est laboriosam. + keywords: Enim unde omnis. + platforms: + - Nam exercitationem consectetur sint. + - Necessitatibus ex ea ullam corrupti. + - Et omnis accusantium. + - Iure modi asperiores ut. + program_type: education + targeting: Quam sed rem quia consequuntur ut. + url: Facere sint totam sapiente vitae numquam ut. responses: "200": description: OK response. @@ -264,21 +367,28 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Inventore impedit unde voluptatem quia ab recusandae. - example: Vel omnis iure. - content: - application/json: - schema: - $ref: '#/components/schemas/GoogleAdsConnection4' - example: - account_id: Ut commodi asperiores. - has_credentials: false - id: Dignissimos non ut sed. - label: Perferendis nam voluptatibus totam. - login_customer_id: Voluptatem similique. - project_id: Deleniti quam. - status: inactive - version: 2550395787104322115 + example: Necessitatibus fugit voluptates pariatur facilis nostrum sint. + example: Possimus qui est numquam omnis nihil. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief4' + example: + copy: Aut et. + event_details: Tenetur velit quaerat aut. + event_slug: Hic delectus. + id: Architecto vel. + keywords: Harum provident quia distinctio ea. + platforms: + - Aliquam consequuntur quo aliquid ducimus. + - Facilis facilis aut suscipit aut odio iste. + - Deleniti earum magni quibusdam. + program_type: education + project_id: Vel autem modi id facere. + status: draft + targeting: Rem harum provident provident quia laboriosam magnam. + url: Maxime ut possimus expedita. + version: 4124348486962351749 "400": description: 'BadRequest: Bad request' content: @@ -297,6 +407,15 @@ paths: example: code: "404" message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. "412": description: 'PreconditionFailed: ETag mismatch' content: @@ -335,13 +454,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-google-ads/set-credential: + /projects/{project_id}/briefs/{brief_id}/approve: post: tags: - - lfx-v2-campaign-service-connections - summary: set-credential-google-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-google-ads + - lfx-v2-campaign-service-briefs + summary: approve-brief lfx-v2-campaign-service-briefs + description: Approve a brief for campaign creation. + operationId: lfx-v2-campaign-service-briefs#approve-brief parameters: - name: project_id in: path @@ -352,21 +471,56 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' - example: - credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: fd5c9608-acd6-49e9-bb2e-de24b6d81a44 + format: uuid + example: 9cfebac2-eca8-4ba0-8c29-1102ebabcfea + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" responses: - "204": - description: No Content response. + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Animi ut aut excepturi temporibus ipsum perferendis. + example: Ipsum quos voluptas iure eum tempore. + content: + application/json: + schema: + $ref: '#/components/schemas/Brief5' + example: + copy: Iusto quidem laudantium. + event_details: Est consequatur eveniet in fuga ut. + event_slug: Velit est quis aliquid aliquam. + id: Consectetur porro quo. + keywords: Ratione consequuntur aut et corrupti. + platforms: + - Aliquam quia officiis hic ea consequatur. + - Dolor velit debitis vitae quae. + - Sunt aperiam ex voluptatibus. + program_type: membership + project_id: Incidunt aut aperiam et ipsam. + status: draft + targeting: Quaerat ut neque expedita nostrum. + url: Voluptate nisi natus sint. + version: 5518996230427104310 "400": description: 'BadRequest: Bad request' content: @@ -385,6 +539,33 @@ paths: example: code: "404" message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. "500": description: 'InternalServerError: Internal server error' content: @@ -405,13 +586,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-google-ads/test: + /projects/{project_id}/briefs/{brief_id}/campaigns: post: tags: - - lfx-v2-campaign-service-connections - summary: test-google-ads lfx-v2-campaign-service-connections - description: Verify the stored Google Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-google-ads + - lfx-v2-campaign-service-briefs + summary: create-campaigns lfx-v2-campaign-service-briefs + description: Create campaigns across the selected platforms (async -> job). + operationId: lfx-v2-campaign-service-briefs#create-campaigns parameters: - name: project_id in: path @@ -422,16 +603,53 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: fca41ae9-5cf1-4249-84ff-3c128c4d9604 + format: uuid + example: edb87f72-22ea-4a62-9cc3-0f9ec1305513 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCampaignsRequestBody' + example: + input: + config: Quia quos ut tempora consequuntur cumque repudiandae. + platforms: + - microsoft-ads + - microsoft-ads + - linkedin-ads responses: - "200": - description: OK response. + "202": + description: Accepted response. content: application/json: schema: - $ref: '#/components/schemas/ConnectionTestResult' + $ref: '#/components/schemas/JobCreateResponse' example: - message: Deserunt ab itaque amet numquam. - ok: true + job_id: Enim non sint praesentium. + platforms: + - Et veritatis voluptas nobis. + - Doloremque architecto ea et. + - Asperiores facere et exercitationem soluta possimus. + - Aliquid et in. + status: queued + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. "404": description: 'NotFound: Resource not found' content: @@ -441,6 +659,15 @@ paths: example: code: "404" message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. "500": description: 'InternalServerError: Internal server error' content: @@ -461,13 +688,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-hubspot: - delete: + /projects/{project_id}/briefs/{brief_id}/campaigns/{campaign_id}: + get: tags: - - lfx-v2-campaign-service-connections - summary: delete-hubspot lfx-v2-campaign-service-connections - description: Soft-delete the project's HubSpot connection. - operationId: lfx-v2-campaign-service-connections#delete-hubspot + - lfx-v2-campaign-service-briefs + summary: get-campaign lfx-v2-campaign-service-briefs + description: Get one campaign under a brief; returns ETag. + operationId: lfx-v2-campaign-service-briefs#get-campaign parameters: - name: project_id in: path @@ -478,10 +705,263 @@ paths: description: Project UUID or slug that scopes the connection example: cncf example: cncf - responses: - "204": - description: No Content response. - "404": + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 40b44584-e849-4e18-9ef0-4ccead532f00 + format: uuid + example: a5be33ff-d12e-479b-ad6a-7cc2ed678259 + - name: campaign_id + in: path + description: Campaign UUID + required: true + schema: + type: string + description: Campaign UUID + example: 3d5136fa-42a8-45c6-bec5-cf457b12b3c2 + format: uuid + example: 01450dd7-92b6-418d-a29c-b38911d10766 + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Mollitia nam aspernatur itaque itaque recusandae. + example: Omnis doloribus eius molestias animi quas ut. + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign2' + example: + brief_id: Illum rerum error incidunt. + campaign_name: Et veritatis quisquam sed ratione et. + id: Quo quidem. + platform: Adipisci quaerat est nobis. + platform_campaign_id: Fugit non fuga rem adipisci. + project_id: Cumque totam nulla aut reprehenderit. + status: Cumque molestiae eos rem aut. + version: 90571920890933503 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-briefs + summary: update-campaign lfx-v2-campaign-service-briefs + description: Replace a campaign (requires If-Match). + operationId: lfx-v2-campaign-service-briefs#update-campaign + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: brief_id + in: path + description: Brief UUID + required: true + schema: + type: string + description: Brief UUID + example: 9a0492f8-2923-44bc-8d2f-16bc941b2873 + format: uuid + example: a22bdbd6-342c-4b01-a5f2-015845ba9835 + - name: campaign_id + in: path + description: Campaign UUID + required: true + schema: + type: string + description: Campaign UUID + example: fde7d17f-00f1-46be-bf9e-fe43cfa50d20 + format: uuid + example: 1192ee6b-f97c-47cc-85c1-a249cf3a9953 + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCampaignRequestBody' + example: + campaign: + campaign_name: Eos doloribus autem corrupti sed voluptatum laborum. + config: Perferendis qui maiores aut reiciendis. + status: Suscipit quas sunt eos nulla. + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Quas odio. + example: Perferendis asperiores. + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign3' + example: + brief_id: Beatae placeat cumque iure veritatis cumque. + campaign_name: Deserunt aliquid omnis explicabo doloremque et sed. + id: Porro architecto. + platform: Ex distinctio ipsum ab assumenda natus. + platform_campaign_id: Esse est. + project_id: Aperiam doloribus aut. + status: Odio sunt totam. + version: 9047518515926792446 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/connection-google-ads: + delete: + tags: + - lfx-v2-campaign-service-connections + summary: delete-google-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's Google Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-google-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + responses: + "204": + description: No Content response. + "404": description: 'NotFound: Resource not found' content: application/json: @@ -513,9 +993,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-hubspot lfx-v2-campaign-service-connections - description: Get the project's HubSpot connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-hubspot + summary: get-google-ads lfx-v2-campaign-service-connections + description: Get the project's Google Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-google-ads parameters: - name: project_id in: path @@ -535,24 +1015,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Necessitatibus quaerat minima a voluptatem ipsa et. - example: Pariatur perferendis explicabo expedita et velit accusamus. + example: Temporibus ut sint ipsam impedit et minima. + example: Vero quibusdam. content: application/json: schema: - $ref: '#/components/schemas/HubspotConnection3' + $ref: '#/components/schemas/GoogleAdsConnection3' example: - account_id: Perspiciatis saepe consequatur doloribus sequi et. - brand_kit: Distinctio vel. + account_id: Perspiciatis id. has_credentials: true - id: Nulla ut laboriosam. - label: Omnis suscipit corrupti reiciendis. - portal_id: Nihil quia omnis eaque aperiam eum. - project_id: Rerum delectus velit. - sender_email: Quam et voluptatibus illum dolorem. - sender_name: Eum dolorum. - status: active - version: 3995988906528985875 + id: Officia et sint numquam accusamus. + label: Vel natus provident. + login_customer_id: Aut et fuga enim nihil nihil quae. + project_id: Eos et architecto voluptas. + status: inactive + version: 270615534997019024 "404": description: 'NotFound: Resource not found' content: @@ -585,9 +1062,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-hubspot lfx-v2-campaign-service-connections - description: Create the project's HubSpot connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-hubspot + summary: create-google-ads lfx-v2-campaign-service-connections + description: Create the project's Google Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-google-ads parameters: - name: project_id in: path @@ -603,17 +1080,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateHubspotRequestBody' + $ref: '#/components/schemas/CreateGoogleAdsRequestBody' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: "8666746580" + label: TLF Main + login_customer_id: "9746983954" credentials: - private_app_token: Numquam ut nesciunt voluptas. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. responses: "201": description: Created response. @@ -623,24 +1100,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Sequi non et. - example: Quibusdam repellendus qui numquam. + example: Maiores impedit consequuntur est fugit. + example: Accusantium et eos. content: application/json: schema: - $ref: '#/components/schemas/HubspotConnection2' + $ref: '#/components/schemas/GoogleAdsConnection2' example: - account_id: Voluptates iste autem veniam et enim unde. - brand_kit: Blanditiis quidem qui iusto sed. - has_credentials: false - id: Nesciunt necessitatibus ex ea ullam corrupti sunt. - label: Modi asperiores ut libero vel. - portal_id: Rem quia consequuntur ut. - project_id: Omnis accusantium ad. - sender_email: Consequatur quos atque voluptates quo sit. - sender_name: Dolor molestias ipsa dolorum est. - status: inactive - version: 1691702820919785934 + account_id: In recusandae maiores et voluptatem. + has_credentials: true + id: Fuga nostrum velit hic excepturi omnis. + label: Odio labore pariatur nobis. + login_customer_id: Doloribus qui consequatur et dignissimos ex. + project_id: Distinctio quos ut unde et atque. + status: error + version: 8118771688807322679 "400": description: 'BadRequest: Bad request' content: @@ -682,9 +1156,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-hubspot lfx-v2-campaign-service-connections - description: Replace the HubSpot connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-hubspot + summary: update-google-ads lfx-v2-campaign-service-connections + description: Replace the Google Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-google-ads parameters: - name: project_id in: path @@ -709,15 +1183,12 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateHubspotRequestBody' + $ref: '#/components/schemas/UpdateGoogleAdsRequestBody' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: "8666746580" + label: TLF Main + login_customer_id: "9746983954" responses: "200": description: OK response. @@ -727,24 +1198,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Deleniti placeat rerum. - example: Recusandae totam soluta. + example: Dolor quia necessitatibus. + example: Excepturi iure voluptas voluptatem. content: application/json: schema: - $ref: '#/components/schemas/HubspotConnection4' + $ref: '#/components/schemas/GoogleAdsConnection4' example: - account_id: Aut ratione sunt nostrum tenetur. - brand_kit: Ab laboriosam quis sapiente ut sit. - has_credentials: true - id: Ipsum quaerat delectus cupiditate sit et. - label: Magni et rerum sed voluptatum. - portal_id: Fugit enim et sint consectetur ea minus. - project_id: Enim est occaecati sed non dolorem distinctio. - sender_email: Ut laborum nostrum veniam qui rerum. - sender_name: Eum fugit repudiandae magnam ut. + account_id: Nobis soluta. + has_credentials: false + id: Vitae perspiciatis eveniet quibusdam nobis ut asperiores. + label: Quis dicta voluptate aut in corporis. + login_customer_id: Deleniti voluptas aperiam vel quae laudantium. + project_id: Aut enim et. status: active - version: 2378889005955898844 + version: 4385483960184879572 "400": description: 'BadRequest: Bad request' content: @@ -801,13 +1269,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-hubspot/set-credential: + /projects/{project_id}/connection-google-ads/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-hubspot lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-hubspot + summary: set-credential-google-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) Google Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-google-ads parameters: - name: project_id in: path @@ -823,10 +1291,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialHubspotRequestBody' + $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. responses: "204": description: No Content response. @@ -868,13 +1339,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-hubspot/test: + /projects/{project_id}/connection-google-ads/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-hubspot lfx-v2-campaign-service-connections - description: Verify the stored HubSpot credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-hubspot + summary: test-google-ads lfx-v2-campaign-service-connections + description: Verify the stored Google Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-google-ads parameters: - name: project_id in: path @@ -893,8 +1364,8 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Tempora error rerum quae nihil. - ok: false + message: Rerum itaque consequatur laboriosam non. + ok: true "404": description: 'NotFound: Resource not found' content: @@ -924,13 +1395,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-linkedin-ads: + /projects/{project_id}/connection-hubspot: delete: tags: - lfx-v2-campaign-service-connections - summary: delete-linkedin-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's LinkedIn Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-linkedin-ads + summary: delete-hubspot lfx-v2-campaign-service-connections + description: Soft-delete the project's HubSpot connection. + operationId: lfx-v2-campaign-service-connections#delete-hubspot parameters: - name: project_id in: path @@ -976,9 +1447,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-linkedin-ads lfx-v2-campaign-service-connections - description: Get the project's LinkedIn Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-linkedin-ads + summary: get-hubspot lfx-v2-campaign-service-connections + description: Get the project's HubSpot connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-hubspot parameters: - name: project_id in: path @@ -998,21 +1469,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Ea eum. - example: Repudiandae quae aut. + example: Sunt odit molestiae ab praesentium enim. + example: Earum non fugiat placeat. content: application/json: schema: - $ref: '#/components/schemas/LinkedinAdsConnection3' + $ref: '#/components/schemas/HubspotConnection3' example: - account_id: Quis repellat tenetur. + account_id: Molestiae suscipit non iusto. + brand_kit: Et cupiditate molestiae ea ullam minima voluptatem. has_credentials: false - id: Et quam veritatis consequatur sed. - label: Ipsum fuga deleniti consequatur. - org_id: Natus laudantium quisquam. - project_id: Tempora aut incidunt incidunt dolorem eaque et. - status: inactive - version: 2296210645404105233 + id: Eaque ut laudantium nostrum ratione. + label: Voluptate itaque fugit rerum cumque. + portal_id: Soluta omnis eius dicta ut. + project_id: Eius laborum. + sender_email: Distinctio dolore velit pariatur. + sender_name: Assumenda perspiciatis dolor porro. + status: error + version: 5347858386956008427 "404": description: 'NotFound: Resource not found' content: @@ -1045,9 +1519,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-linkedin-ads lfx-v2-campaign-service-connections - description: Create the project's LinkedIn Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-linkedin-ads + summary: create-hubspot lfx-v2-campaign-service-connections + description: Create the project's HubSpot connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-hubspot parameters: - name: project_id in: path @@ -1063,14 +1537,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateLinkedinAdsRequestBody' + $ref: '#/components/schemas/CreateHubspotRequestBody' example: config: - account_id: "538170226" - label: Fugit voluptatem. - org_id: "208777" + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + private_app_token: Vitae vel omnis autem accusamus mollitia. responses: "201": description: Created response. @@ -1080,21 +1557,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Voluptatem doloremque ullam consequatur ut. - example: Ipsa magnam cum veritatis mollitia vel. + example: Ad a et inventore tempora facilis dolores. + example: Animi similique voluptatem vel voluptatibus. content: application/json: schema: - $ref: '#/components/schemas/LinkedinAdsConnection2' + $ref: '#/components/schemas/HubspotConnection2' example: - account_id: Totam quasi vero. + account_id: Esse voluptate eum laboriosam distinctio non. + brand_kit: Harum eos eveniet ut. has_credentials: false - id: Eum quia molestiae. - label: Est quos. - org_id: Est est id repellat quod deleniti cupiditate. - project_id: Rerum delectus tempora culpa. - status: error - version: 2086671994798543080 + id: Rem quia vel ipsam dolores. + label: Ullam facilis ab sapiente voluptatem. + portal_id: Id aut et adipisci quo ut. + project_id: Et qui ut libero numquam. + sender_email: Deserunt voluptates ut culpa saepe necessitatibus veritatis. + sender_name: Enim id voluptas aperiam distinctio. + status: inactive + version: 8626906144120400240 "400": description: 'BadRequest: Bad request' content: @@ -1136,9 +1616,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-linkedin-ads lfx-v2-campaign-service-connections - description: Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-linkedin-ads + summary: update-hubspot lfx-v2-campaign-service-connections + description: Replace the HubSpot connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-hubspot parameters: - name: project_id in: path @@ -1163,12 +1643,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateLinkedinAdsRequestBody' + $ref: '#/components/schemas/UpdateHubspotRequestBody' example: config: - account_id: "538170226" - label: Fugit voluptatem. - org_id: "208777" + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. responses: "200": description: OK response. @@ -1178,21 +1661,24 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Tempore rem quo fuga accusantium voluptas ratione. - example: Numquam optio possimus velit quod. + example: Amet quam consequuntur nulla quaerat. + example: Commodi dolores autem doloribus laborum et. content: application/json: schema: - $ref: '#/components/schemas/LinkedinAdsConnection4' + $ref: '#/components/schemas/HubspotConnection4' example: - account_id: Ipsa rerum odit et nostrum veritatis dolores. - has_credentials: false - id: Aut et voluptas ut officiis. - label: Debitis voluptatem quam occaecati. - org_id: Et tenetur sequi dolorum porro. - project_id: In autem tempore ut esse. - status: deleted - version: 7961829707957443728 + account_id: Non unde. + brand_kit: Incidunt velit provident perspiciatis. + has_credentials: true + id: Quia minus vero. + label: Fugit alias nisi qui maiores. + portal_id: Doloremque explicabo ut. + project_id: Ut quis animi molestiae. + sender_email: Odio odio sed minus quia quia ea. + sender_name: Autem iste ea in ipsa. + status: error + version: 7175407383173057293 "400": description: 'BadRequest: Bad request' content: @@ -1249,13 +1735,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-linkedin-ads/set-credential: + /projects/{project_id}/connection-hubspot/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-linkedin-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-linkedin-ads + summary: set-credential-hubspot lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) HubSpot credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-hubspot parameters: - name: project_id in: path @@ -1271,10 +1757,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialLinkedinAdsRequestBody' + $ref: '#/components/schemas/SetCredentialHubspotRequestBody' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + private_app_token: Vitae vel omnis autem accusamus mollitia. responses: "204": description: No Content response. @@ -1316,13 +1802,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-linkedin-ads/test: + /projects/{project_id}/connection-hubspot/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-linkedin-ads lfx-v2-campaign-service-connections - description: Verify the stored LinkedIn Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-linkedin-ads + summary: test-hubspot lfx-v2-campaign-service-connections + description: Verify the stored HubSpot credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-hubspot parameters: - name: project_id in: path @@ -1341,7 +1827,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Dolorum voluptates placeat rerum. + message: Sunt itaque omnis ut ex sunt sit. ok: true "404": description: 'NotFound: Resource not found' @@ -1372,13 +1858,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-meta-ads: + /projects/{project_id}/connection-linkedin-ads: delete: tags: - lfx-v2-campaign-service-connections - summary: delete-meta-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's Meta Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-meta-ads + summary: delete-linkedin-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's LinkedIn Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-linkedin-ads parameters: - name: project_id in: path @@ -1424,9 +1910,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-meta-ads lfx-v2-campaign-service-connections - description: Get the project's Meta Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-meta-ads + summary: get-linkedin-ads lfx-v2-campaign-service-connections + description: Get the project's LinkedIn Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-linkedin-ads parameters: - name: project_id in: path @@ -1446,22 +1932,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Vel necessitatibus vel voluptates tenetur veniam repellat. - example: Aut repudiandae voluptatibus odit autem. + example: Quo porro asperiores eum officiis. + example: Autem assumenda dolor qui ut facere. content: application/json: schema: - $ref: '#/components/schemas/MetaAdsConnection3' + $ref: '#/components/schemas/LinkedinAdsConnection3' example: - account_id: Voluptatem cupiditate voluptas aliquam facilis iste sed. - app_id: A sed exercitationem ut. - has_credentials: false - id: Expedita unde molestiae. - label: Velit ullam. - page_id: Voluptatum a. - project_id: Voluptas dolor et incidunt pariatur quod odit. - status: active - version: 6376430019451136769 + account_id: Et eum est molestiae necessitatibus magni. + has_credentials: true + id: Voluptatibus deserunt. + label: Et eligendi et numquam dolorum consequatur in. + org_id: Deserunt ipsum dolorum quas error. + project_id: Necessitatibus eos. + status: error + version: 3367774197321419764 "404": description: 'NotFound: Resource not found' content: @@ -1494,9 +1979,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-meta-ads lfx-v2-campaign-service-connections - description: Create the project's Meta Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-meta-ads + summary: create-linkedin-ads lfx-v2-campaign-service-connections + description: Create the project's LinkedIn Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-linkedin-ads parameters: - name: project_id in: path @@ -1512,16 +1997,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateMetaAdsRequestBody' + $ref: '#/components/schemas/CreateLinkedinAdsRequestBody' example: config: - account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + account_id: "538170226" + label: Temporibus et provident maiores neque. + org_id: "208777" credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Illo minima eaque earum. responses: "201": description: Created response. @@ -1531,22 +2014,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Perspiciatis quam iste possimus ipsam. - example: Eveniet quia voluptatem impedit cumque doloribus. + example: Est similique rerum consequatur. + example: Aut consectetur mollitia repellat. content: application/json: schema: - $ref: '#/components/schemas/MetaAdsConnection2' + $ref: '#/components/schemas/LinkedinAdsConnection2' example: - account_id: Fuga ut expedita voluptatum sit. - app_id: Quia ut atque. + account_id: Qui enim cumque saepe. has_credentials: true - id: Quis perspiciatis. - label: Similique vel nobis non molestias. - page_id: Velit et. - project_id: Nesciunt unde ullam occaecati maxime. - status: active - version: 8123871071866488705 + id: Eligendi tempore provident dicta temporibus debitis porro. + label: Aperiam quis quis reprehenderit non quae. + org_id: Accusantium aut. + project_id: Odio incidunt distinctio sint. + status: error + version: 6613376414740649303 "400": description: 'BadRequest: Bad request' content: @@ -1588,9 +2070,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-meta-ads lfx-v2-campaign-service-connections - description: Replace the Meta Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-meta-ads + summary: update-linkedin-ads lfx-v2-campaign-service-connections + description: Replace the LinkedIn Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-linkedin-ads parameters: - name: project_id in: path @@ -1615,13 +2097,12 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateMetaAdsRequestBody' + $ref: '#/components/schemas/UpdateLinkedinAdsRequestBody' example: config: - account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + account_id: "538170226" + label: Temporibus et provident maiores neque. + org_id: "208777" responses: "200": description: OK response. @@ -1631,22 +2112,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Dicta qui. - example: Voluptate dolorum id ut molestias id. + example: Qui veniam architecto deleniti adipisci quas itaque. + example: Fuga rerum iure possimus et. content: application/json: schema: - $ref: '#/components/schemas/MetaAdsConnection4' + $ref: '#/components/schemas/LinkedinAdsConnection4' example: - account_id: Quos quo eum dignissimos iure praesentium. - app_id: Voluptas ratione delectus ut adipisci. - has_credentials: false - id: Doloremque itaque eius magni autem. - label: Ex tempore. - page_id: Qui mollitia perferendis eveniet voluptas est et. - project_id: Ullam tenetur quia totam magni sunt repellat. - status: inactive - version: 7772855560046758577 + account_id: Est rerum. + has_credentials: true + id: Et accusamus sed non sequi. + label: Sunt est labore libero. + org_id: Natus expedita aspernatur consequuntur quod ducimus molestias. + project_id: Assumenda quam deleniti et et nam quia. + status: error + version: 313762477408274568 "400": description: 'BadRequest: Bad request' content: @@ -1703,13 +2183,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-meta-ads/set-credential: + /projects/{project_id}/connection-linkedin-ads/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-meta-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-meta-ads + summary: set-credential-linkedin-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) LinkedIn Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-linkedin-ads parameters: - name: project_id in: path @@ -1725,11 +2205,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialMetaAdsRequestBody' + $ref: '#/components/schemas/SetCredentialLinkedinAdsRequestBody' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Illo minima eaque earum. responses: "204": description: No Content response. @@ -1771,13 +2250,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-meta-ads/test: + /projects/{project_id}/connection-linkedin-ads/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-meta-ads lfx-v2-campaign-service-connections - description: Verify the stored Meta Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-meta-ads + summary: test-linkedin-ads lfx-v2-campaign-service-connections + description: Verify the stored LinkedIn Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-linkedin-ads parameters: - name: project_id in: path @@ -1796,7 +2275,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Aliquam animi unde. + message: Voluptatem iste nisi in aperiam. ok: true "404": description: 'NotFound: Resource not found' @@ -1827,13 +2306,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-microsoft-ads: + /projects/{project_id}/connection-meta-ads: delete: tags: - lfx-v2-campaign-service-connections - summary: delete-microsoft-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's Microsoft Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-microsoft-ads + summary: delete-meta-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's Meta Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-meta-ads parameters: - name: project_id in: path @@ -1879,9 +2358,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-microsoft-ads lfx-v2-campaign-service-connections - description: Get the project's Microsoft Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-microsoft-ads + summary: get-meta-ads lfx-v2-campaign-service-connections + description: Get the project's Meta Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-meta-ads parameters: - name: project_id in: path @@ -1901,21 +2380,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Non eos. - example: Enim voluptatem inventore quasi. + example: Et voluptatum excepturi nemo. + example: Veniam doloremque dolores voluptatum aut mollitia aperiam. content: application/json: schema: - $ref: '#/components/schemas/MicrosoftAdsConnection3' + $ref: '#/components/schemas/MetaAdsConnection3' example: - account_id: Optio dolorem omnis. - customer_id: Rerum adipisci minima quibusdam autem accusantium. - has_credentials: false - id: Veniam eum ut libero quibusdam debitis. - label: Commodi quam voluptates. - project_id: Ut consectetur. - status: deleted - version: 6203614951491514230 + account_id: Est distinctio sunt aliquid harum quis nam. + app_id: Consectetur sint error ipsam maiores. + has_credentials: true + id: Facilis qui ducimus modi ut culpa. + label: Eius error autem. + page_id: Rerum illo mollitia odio et dolor. + project_id: Blanditiis officiis quia quisquam corrupti distinctio. + status: active + version: 2737266573164142326 "404": description: 'NotFound: Resource not found' content: @@ -1948,9 +2428,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-microsoft-ads lfx-v2-campaign-service-connections - description: Create the project's Microsoft Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-microsoft-ads + summary: create-meta-ads lfx-v2-campaign-service-connections + description: Create the project's Meta Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-meta-ads parameters: - name: project_id in: path @@ -1966,17 +2446,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateMicrosoftAdsRequestBody' + $ref: '#/components/schemas/CreateMetaAdsRequestBody' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: act_193556282970417 + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. responses: "201": description: Created response. @@ -1986,21 +2465,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Delectus enim quas id porro. - example: Quo repudiandae. + example: Hic voluptatibus accusamus vel. + example: Quo maxime laboriosam alias molestiae ea. content: application/json: schema: - $ref: '#/components/schemas/MicrosoftAdsConnection2' + $ref: '#/components/schemas/MetaAdsConnection2' example: - account_id: Exercitationem rerum iste ducimus. - customer_id: Quidem velit est explicabo est sed. - has_credentials: true - id: Modi rerum sint. - label: Dolorem dolor fugiat quia. - project_id: Labore iste voluptates quos. - status: inactive - version: 8271015532862321776 + account_id: Sed numquam necessitatibus laudantium est ut. + app_id: Voluptatem possimus et corrupti et nobis. + has_credentials: false + id: Nostrum corporis quasi magnam. + label: Ipsa quo vel architecto quas eligendi impedit. + page_id: Qui possimus. + project_id: Qui quidem exercitationem. + status: deleted + version: 5650595339177390494 "400": description: 'BadRequest: Bad request' content: @@ -2042,9 +2522,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-microsoft-ads lfx-v2-campaign-service-connections - description: Replace the Microsoft Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-microsoft-ads + summary: update-meta-ads lfx-v2-campaign-service-connections + description: Replace the Meta Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-meta-ads parameters: - name: project_id in: path @@ -2069,12 +2549,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateMicrosoftAdsRequestBody' + $ref: '#/components/schemas/UpdateMetaAdsRequestBody' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: act_193556282970417 + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. responses: "200": description: OK response. @@ -2084,21 +2565,22 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Aut omnis itaque ut vero voluptatibus. - example: Vero dolorem qui. + example: Porro et fuga facere est qui vero. + example: Molestias quisquam est. content: application/json: schema: - $ref: '#/components/schemas/MicrosoftAdsConnection4' + $ref: '#/components/schemas/MetaAdsConnection4' example: - account_id: Maxime fugiat illo. - customer_id: Minus est ut. + account_id: Porro voluptate. + app_id: Velit dolores omnis placeat unde distinctio ducimus. has_credentials: false - id: Tempora recusandae. - label: Reiciendis architecto atque maxime soluta dolorem ducimus. - project_id: Itaque harum. - status: deleted - version: 6177609042943350629 + id: Perspiciatis deleniti. + label: Facilis suscipit. + page_id: Eligendi quasi eos ipsa et dolore. + project_id: Non sint inventore iste iure ut. + status: error + version: 7363633618316247458 "400": description: 'BadRequest: Bad request' content: @@ -2155,13 +2637,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-microsoft-ads/set-credential: + /projects/{project_id}/connection-meta-ads/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-microsoft-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-microsoft-ads + summary: set-credential-meta-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) Meta Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-meta-ads parameters: - name: project_id in: path @@ -2177,13 +2659,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' + $ref: '#/components/schemas/SetCredentialMetaAdsRequestBody' example: credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. responses: "204": description: No Content response. @@ -2225,13 +2705,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-microsoft-ads/test: + /projects/{project_id}/connection-meta-ads/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-microsoft-ads lfx-v2-campaign-service-connections - description: Verify the stored Microsoft Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-microsoft-ads + summary: test-meta-ads lfx-v2-campaign-service-connections + description: Verify the stored Meta Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-meta-ads parameters: - name: project_id in: path @@ -2250,7 +2730,7 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Ullam fugiat. + message: Dignissimos non sed omnis officiis et. ok: true "404": description: 'NotFound: Resource not found' @@ -2281,13 +2761,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-reddit-ads: + /projects/{project_id}/connection-microsoft-ads: delete: tags: - lfx-v2-campaign-service-connections - summary: delete-reddit-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's Reddit Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-reddit-ads + summary: delete-microsoft-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's Microsoft Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-microsoft-ads parameters: - name: project_id in: path @@ -2333,9 +2813,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-reddit-ads lfx-v2-campaign-service-connections - description: Get the project's Reddit Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-reddit-ads + summary: get-microsoft-ads lfx-v2-campaign-service-connections + description: Get the project's Microsoft Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-microsoft-ads parameters: - name: project_id in: path @@ -2355,20 +2835,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Autem aliquam voluptatibus assumenda mollitia molestias eos. - example: Fugit eum et sint possimus omnis. + example: Soluta porro distinctio cupiditate. + example: Voluptatem optio perspiciatis reprehenderit. content: application/json: schema: - $ref: '#/components/schemas/RedditAdsConnection3' + $ref: '#/components/schemas/MicrosoftAdsConnection3' example: - account_id: Amet iusto provident sequi est. + account_id: Deserunt quod. + customer_id: Nisi blanditiis eveniet quasi ea. has_credentials: true - id: Voluptas non accusantium. - label: Sit dolorum excepturi quibusdam. - project_id: Architecto consequatur enim magnam eius voluptas voluptatibus. - status: deleted - version: 1322493338651594921 + id: Perspiciatis porro. + label: Rem error. + project_id: Distinctio amet enim. + status: active + version: 3519020753859014023 "404": description: 'NotFound: Resource not found' content: @@ -2401,9 +2882,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-reddit-ads lfx-v2-campaign-service-connections - description: Create the project's Reddit Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-reddit-ads + summary: create-microsoft-ads lfx-v2-campaign-service-connections + description: Create the project's Microsoft Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-microsoft-ads parameters: - name: project_id in: path @@ -2419,15 +2900,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateRedditAdsRequestBody' + $ref: '#/components/schemas/CreateMicrosoftAdsRequestBody' example: config: - account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Officiis aut fugiat optio ut. + client_secret: Et voluptas tenetur nobis facere. + developer_token: Blanditiis mollitia dolores illo nostrum magnam. + refresh_token: Quo corporis recusandae dicta quibusdam. responses: "201": description: Created response. @@ -2437,20 +2920,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Ipsa aut. - example: Impedit vitae molestiae. + example: Ipsum ullam quis est incidunt debitis qui. + example: Quia sunt dignissimos pariatur dolorem natus. content: application/json: schema: - $ref: '#/components/schemas/RedditAdsConnection2' + $ref: '#/components/schemas/MicrosoftAdsConnection2' example: - account_id: Ut ea alias. - has_credentials: false - id: Explicabo cupiditate rerum. - label: Hic vero et. - project_id: Incidunt ipsum est perspiciatis soluta voluptas est. - status: inactive - version: 347677279926224703 + account_id: Eum et ipsum expedita reprehenderit eos. + customer_id: Deleniti nostrum. + has_credentials: true + id: Omnis nobis. + label: Rerum explicabo. + project_id: Deserunt repellendus corrupti itaque quo sit. + status: deleted + version: 7665319414608673783 "400": description: 'BadRequest: Bad request' content: @@ -2492,9 +2976,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-reddit-ads lfx-v2-campaign-service-connections - description: Replace the Reddit Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-reddit-ads + summary: update-microsoft-ads lfx-v2-campaign-service-connections + description: Replace the Microsoft Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-microsoft-ads parameters: - name: project_id in: path @@ -2519,11 +3003,12 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateRedditAdsRequestBody' + $ref: '#/components/schemas/UpdateMicrosoftAdsRequestBody' example: config: - account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. responses: "200": description: OK response. @@ -2533,20 +3018,21 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Neque hic voluptatem ad similique sunt. - example: Ratione impedit recusandae dolor consequatur. + example: Necessitatibus quam. + example: Assumenda repellat et rerum cum. content: application/json: schema: - $ref: '#/components/schemas/RedditAdsConnection4' + $ref: '#/components/schemas/MicrosoftAdsConnection4' example: - account_id: Saepe pariatur. - has_credentials: true - id: Vero quam. - label: Eveniet soluta adipisci. - project_id: Neque tempore voluptatibus quo quod nesciunt. + account_id: Labore omnis provident nihil aut rerum est. + customer_id: Corporis sint non dolorem repellendus. + has_credentials: false + id: Vel voluptate necessitatibus. + label: Mollitia aut rerum quis et modi. + project_id: Et et itaque sunt. status: active - version: 779913646680513111 + version: 2920255620636300538 "400": description: 'BadRequest: Bad request' content: @@ -2603,13 +3089,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-reddit-ads/set-credential: + /projects/{project_id}/connection-microsoft-ads/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-reddit-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-reddit-ads + summary: set-credential-microsoft-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) Microsoft Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-microsoft-ads parameters: - name: project_id in: path @@ -2625,12 +3111,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialRedditAdsRequestBody' + $ref: '#/components/schemas/SetCredentialGoogleAdsRequestBody' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Officiis aut fugiat optio ut. + client_secret: Et voluptas tenetur nobis facere. + developer_token: Blanditiis mollitia dolores illo nostrum magnam. + refresh_token: Quo corporis recusandae dicta quibusdam. responses: "204": description: No Content response. @@ -2672,13 +3159,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-reddit-ads/test: + /projects/{project_id}/connection-microsoft-ads/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-reddit-ads lfx-v2-campaign-service-connections - description: Verify the stored Reddit Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-reddit-ads + summary: test-microsoft-ads lfx-v2-campaign-service-connections + description: Verify the stored Microsoft Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-microsoft-ads parameters: - name: project_id in: path @@ -2697,8 +3184,8 @@ paths: schema: $ref: '#/components/schemas/ConnectionTestResult' example: - message: Repudiandae veniam soluta alias natus aut. - ok: true + message: Distinctio dolor aut assumenda fugiat quia. + ok: false "404": description: 'NotFound: Resource not found' content: @@ -2728,13 +3215,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-twitter-ads: + /projects/{project_id}/connection-reddit-ads: delete: tags: - lfx-v2-campaign-service-connections - summary: delete-twitter-ads lfx-v2-campaign-service-connections - description: Soft-delete the project's X/Twitter Ads connection. - operationId: lfx-v2-campaign-service-connections#delete-twitter-ads + summary: delete-reddit-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's Reddit Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-reddit-ads parameters: - name: project_id in: path @@ -2780,9 +3267,9 @@ paths: get: tags: - lfx-v2-campaign-service-connections - summary: get-twitter-ads lfx-v2-campaign-service-connections - description: Get the project's X/Twitter Ads connection (credentials redacted; returns ETag). - operationId: lfx-v2-campaign-service-connections#get-twitter-ads + summary: get-reddit-ads lfx-v2-campaign-service-connections + description: Get the project's Reddit Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-reddit-ads parameters: - name: project_id in: path @@ -2802,21 +3289,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Enim esse dolores. - example: Est perferendis eos et cumque nulla. + example: Et et. + example: At qui error totam aut. content: application/json: schema: - $ref: '#/components/schemas/TwitterAdsConnection3' + $ref: '#/components/schemas/RedditAdsConnection3' example: - account_id: Et tempore. - funding_instrument_id: Est et id laudantium magnam ratione. - has_credentials: false - id: Ipsum deserunt ut. - label: Ut nihil quo tenetur doloribus fugiat natus. - project_id: Sed ea. - status: error - version: 6360932954176398686 + account_id: Unde dicta autem dolorem qui explicabo. + has_credentials: true + id: Omnis neque autem ex eos. + label: A necessitatibus molestiae. + project_id: Eum et sint corrupti eos. + status: deleted + version: 4219814008454412160 "404": description: 'NotFound: Resource not found' content: @@ -2849,9 +3335,9 @@ paths: post: tags: - lfx-v2-campaign-service-connections - summary: create-twitter-ads lfx-v2-campaign-service-connections - description: Create the project's X/Twitter Ads connection (singleton; 409 if one already exists). - operationId: lfx-v2-campaign-service-connections#create-twitter-ads + summary: create-reddit-ads lfx-v2-campaign-service-connections + description: Create the project's Reddit Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-reddit-ads parameters: - name: project_id in: path @@ -2867,17 +3353,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateTwitterAdsRequestBody' + $ref: '#/components/schemas/CreateRedditAdsRequestBody' example: config: - account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + account_id: t2_gv9wtbfa + label: Laboriosam qui id enim. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. responses: "201": description: Created response. @@ -2887,21 +3371,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Vel temporibus sunt nulla esse sed. - example: Facere sed ea qui pariatur adipisci. + example: Quos atque dolorem error fugiat qui. + example: Enim molestiae suscipit sequi et. content: application/json: schema: - $ref: '#/components/schemas/TwitterAdsConnection2' + $ref: '#/components/schemas/RedditAdsConnection2' example: - account_id: Totam sequi. - funding_instrument_id: Labore ut iusto dolores. - has_credentials: false - id: Dolor ut. - label: Porro sed minus illo rerum ut. - project_id: Veniam maiores dolorum sit et. + account_id: Pariatur debitis. + has_credentials: true + id: Est dolor ut corporis. + label: Illo vitae occaecati omnis. + project_id: Cupiditate ut illo dolor quaerat totam. status: active - version: 5887915076445143491 + version: 1381780689877724774 "400": description: 'BadRequest: Bad request' content: @@ -2943,9 +3426,9 @@ paths: put: tags: - lfx-v2-campaign-service-connections - summary: update-twitter-ads lfx-v2-campaign-service-connections - description: Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials). - operationId: lfx-v2-campaign-service-connections#update-twitter-ads + summary: update-reddit-ads lfx-v2-campaign-service-connections + description: Replace the Reddit Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-reddit-ads parameters: - name: project_id in: path @@ -2970,12 +3453,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateTwitterAdsRequestBody' + $ref: '#/components/schemas/UpdateRedditAdsRequestBody' example: config: - account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + account_id: t2_gv9wtbfa + label: Laboriosam qui id enim. responses: "200": description: OK response. @@ -2985,21 +3467,20 @@ paths: schema: type: string description: ETag header value (mirrors version) - example: Neque maiores quo aliquam in rem. - example: Voluptatem possimus veniam dignissimos consequatur. + example: Veritatis culpa quasi qui iure consequuntur porro. + example: Nobis quas sunt dolorum. content: application/json: schema: - $ref: '#/components/schemas/TwitterAdsConnection4' + $ref: '#/components/schemas/RedditAdsConnection4' example: - account_id: Est dolorem eos amet dolorem sed. - funding_instrument_id: Quia porro. - has_credentials: false - id: Ab nobis fuga atque inventore fugiat. - label: Quod blanditiis et. - project_id: Laboriosam maxime facere culpa modi. - status: error - version: 796228352115418506 + account_id: Perferendis velit atque. + has_credentials: true + id: Ut similique ea minima magnam non ut. + label: Est dolores est et recusandae nihil. + project_id: Iure nam. + status: active + version: 4591742417728159020 "400": description: 'BadRequest: Bad request' content: @@ -3056,13 +3537,13 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-twitter-ads/set-credential: + /projects/{project_id}/connection-reddit-ads/set-credential: post: tags: - lfx-v2-campaign-service-connections - summary: set-credential-twitter-ads lfx-v2-campaign-service-connections - description: Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. - operationId: lfx-v2-campaign-service-connections#set-credential-twitter-ads + summary: set-credential-reddit-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) Reddit Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-reddit-ads parameters: - name: project_id in: path @@ -3078,13 +3559,12 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SetCredentialTwitterAdsRequestBody' + $ref: '#/components/schemas/SetCredentialRedditAdsRequestBody' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. responses: "204": description: No Content response. @@ -3126,13 +3606,186 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /projects/{project_id}/connection-twitter-ads/test: + /projects/{project_id}/connection-reddit-ads/test: post: tags: - lfx-v2-campaign-service-connections - summary: test-twitter-ads lfx-v2-campaign-service-connections - description: Verify the stored X/Twitter Ads credential against the provider. - operationId: lfx-v2-campaign-service-connections#test-twitter-ads + summary: test-reddit-ads lfx-v2-campaign-service-connections + description: Verify the stored Reddit Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-reddit-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectionTestResult' + example: + message: Adipisci sint enim. + ok: false + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/connection-twitter-ads: + delete: + tags: + - lfx-v2-campaign-service-connections + summary: delete-twitter-ads lfx-v2-campaign-service-connections + description: Soft-delete the project's X/Twitter Ads connection. + operationId: lfx-v2-campaign-service-connections#delete-twitter-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + responses: + "204": + description: No Content response. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + get: + tags: + - lfx-v2-campaign-service-connections + summary: get-twitter-ads lfx-v2-campaign-service-connections + description: Get the project's X/Twitter Ads connection (credentials redacted; returns ETag). + operationId: lfx-v2-campaign-service-connections#get-twitter-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Quasi vel sint nisi laborum laboriosam assumenda. + example: Ut totam quia ipsam quia ut quibusdam. + content: + application/json: + schema: + $ref: '#/components/schemas/TwitterAdsConnection3' + example: + account_id: Magni consequatur dignissimos itaque quia. + funding_instrument_id: Et voluptatem deserunt consequuntur omnis sed dolore. + has_credentials: true + id: Repellat assumenda nesciunt distinctio eaque aliquam. + label: Et reprehenderit dignissimos. + project_id: Veniam consectetur deserunt in hic dolorum facere. + status: inactive + version: 7072791787806691868 + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + post: + tags: + - lfx-v2-campaign-service-connections + summary: create-twitter-ads lfx-v2-campaign-service-connections + description: Create the project's X/Twitter Ads connection (singleton; 409 if one already exists). + operationId: lfx-v2-campaign-service-connections#create-twitter-ads parameters: - name: project_id in: path @@ -3140,67 +3793,1155 @@ paths: required: true schema: type: string - description: Project UUID or slug that scopes the connection - example: cncf - example: cncf - responses: - "200": - description: OK response. - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectionTestResult' - example: - message: Quo aliquid nobis. - ok: true - "404": - description: 'NotFound: Resource not found' - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundError' - example: - code: "404" - message: The connection was not found. - "500": - description: 'InternalServerError: Internal server error' - content: - application/json: - schema: - $ref: '#/components/schemas/InternalServerError' - example: - code: "500" - message: An internal server error occurred. - "503": - description: 'ServiceUnavailable: Service unavailable' - content: - application/json: - schema: - $ref: '#/components/schemas/ConnServiceUnavailableError' - example: - code: "503" - message: The service is unavailable. - security: - - jwt_header_Authorization: [] -components: - schemas: - BadRequestError: + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTwitterAdsRequestBody' + example: + config: + account_id: 8r7gb + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. + credentials: + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. + responses: + "201": + description: Created response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Eum aut delectus voluptatem. + example: Repudiandae sequi. + content: + application/json: + schema: + $ref: '#/components/schemas/TwitterAdsConnection2' + example: + account_id: Illum illum dolores tenetur ut illum. + funding_instrument_id: Delectus est aut et rerum in expedita. + has_credentials: true + id: Omnis consequatur. + label: Voluptatum ducimus tempore quae eum consequatur dolore. + project_id: Cum non atque aut autem sint. + status: active + version: 1365614189000530579 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "409": + description: 'Conflict: A connection already exists for this provider on the project' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + put: + tags: + - lfx-v2-campaign-service-connections + summary: update-twitter-ads lfx-v2-campaign-service-connections + description: Replace the X/Twitter Ads connection config (requires If-Match; does not set credentials). + operationId: lfx-v2-campaign-service-connections#update-twitter-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: If-Match + in: header + description: If-Match header carrying the current ETag/version + allowEmptyValue: true + schema: + type: string + description: If-Match header carrying the current ETag/version + example: "3" + example: "3" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateTwitterAdsRequestBody' + example: + config: + account_id: 8r7gb + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. + responses: + "200": + description: OK response. + headers: + ETag: + description: ETag header value (mirrors version) + schema: + type: string + description: ETag header value (mirrors version) + example: Delectus vel. + example: Iure doloremque vel et aut. + content: + application/json: + schema: + $ref: '#/components/schemas/TwitterAdsConnection4' + example: + account_id: Quam officiis aut sit architecto ut unde. + funding_instrument_id: Voluptates atque. + has_credentials: false + id: Sunt qui cupiditate ullam ab suscipit et. + label: Assumenda dignissimos beatae vitae aut ea dolor. + project_id: Harum corporis deleniti doloribus. + status: active + version: 7481335546350109928 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "412": + description: 'PreconditionFailed: ETag mismatch' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionFailedError' + example: + code: "412" + message: The supplied ETag does not match the current version. + "428": + description: 'PreconditionRequired: If-Match header required' + content: + application/json: + schema: + $ref: '#/components/schemas/PreconditionRequiredError' + example: + code: "428" + message: An If-Match header is required. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/connection-twitter-ads/set-credential: + post: + tags: + - lfx-v2-campaign-service-connections + summary: set-credential-twitter-ads lfx-v2-campaign-service-connections + description: Replace the stored (encrypted) X/Twitter Ads credential. Separate from update so credential replacement is independently permissioned and audited. Not a rotate — the service does not generate or swap secrets upstream. + operationId: lfx-v2-campaign-service-connections#set-credential-twitter-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SetCredentialTwitterAdsRequestBody' + example: + credentials: + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. + responses: + "204": + description: No Content response. + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/connection-twitter-ads/test: + post: + tags: + - lfx-v2-campaign-service-connections + summary: test-twitter-ads lfx-v2-campaign-service-connections + description: Verify the stored X/Twitter Ads credential against the provider. + operationId: lfx-v2-campaign-service-connections#test-twitter-ads + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectionTestResult' + example: + message: Voluptate reprehenderit. + ok: false + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /projects/{project_id}/jobs/{job_id}: + get: + tags: + - lfx-v2-campaign-service-briefs + summary: get-job lfx-v2-campaign-service-briefs + description: Poll campaign-creation job status. + operationId: lfx-v2-campaign-service-briefs#get-job + parameters: + - name: project_id + in: path + description: Project UUID or slug that scopes the connection + required: true + schema: + type: string + description: Project UUID or slug that scopes the connection + example: cncf + example: cncf + - name: job_id + in: path + description: Job UUID + required: true + schema: + type: string + description: Job UUID + example: 35d610c9-3332-4801-9b65-11002ee56afa + format: uuid + example: a22e3d45-a3ff-4bb1-9983-d7ce7173fde7 + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/JobPollResponse' + example: + error: Aut aut ducimus dolores. + job_id: Accusantium enim temporibus dolor ipsam. + result: + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + - campaign_id: Quisquam voluptatibus sint quia. + error: Tempore nemo illum ut maiores. + ok: false + platform: Officiis est. + status: failed + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + code: "400" + message: The request was invalid. + "404": + description: 'NotFound: Resource not found' + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundError' + example: + code: "404" + message: The connection was not found. + "409": + description: 'Conflict: Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + example: + code: "409" + message: A connection for this provider already exists on the project. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + code: "500" + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ConnServiceUnavailableError' + example: + code: "503" + message: The service is unavailable. + security: + - jwt_header_Authorization: [] +components: + schemas: + BadRequestError: + type: object + properties: + code: + type: string + description: HTTP status code + example: "400" + message: + type: string + description: Error message + example: The request was invalid. + example: + code: "400" + message: The request was invalid. + required: + - code + - message + Brief: + type: object + properties: + copy: + description: Ad copy + example: Eligendi ipsam aut vel atque. + etag: + type: string + description: ETag header value (mirrors version) + example: In voluptas. + event_details: + description: Extracted event/course details + example: Rerum cum eaque nesciunt est totam inventore. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Exercitationem eligendi magni quidem. + id: + type: string + description: Brief UUID + example: Amet quaerat modi. + keywords: + description: Keyword list + example: Reiciendis sit minima. + platforms: + type: array + items: + type: string + example: Perspiciatis alias et amet ex molestiae. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Libero et consequuntur suscipit distinctio pariatur mollitia. + - Autem quo tempora voluptate fuga. + - Distinctio maiores mollitia aut. + program_type: + type: string + description: Funnel context + example: events + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Eum totam voluptatem eos. + status: + type: string + description: Lifecycle status + example: archived + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Unde et. + url: + type: string + description: Event/course page URL + example: In quia. + version: + type: integer + description: Optimistic-concurrency version + example: 1675828466631258091 + format: int64 + example: + copy: Magni eos voluptatem sint hic ut. + etag: Nisi repudiandae. + event_details: Molestiae eius in reprehenderit nobis hic. + event_slug: Est natus sequi beatae. + id: Placeat dolores numquam molestias quibusdam aliquid. + keywords: Harum aliquam sunt eum. + platforms: + - Similique omnis. + - Aliquid dignissimos minima aut fugit. + - Et iure laudantium quia est molestiae. + - Ipsum neque sapiente aliquid quidem velit. + program_type: events + project_id: Fuga est aliquam debitis. + status: draft + targeting: Eaque occaecati. + url: Aut omnis labore et quis consequatur dolores. + version: 1167300153394787715 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief2: + type: object + properties: + copy: + description: Ad copy + example: Quo veritatis nisi velit distinctio animi. + event_details: + description: Extracted event/course details + example: Sapiente perspiciatis dolores dignissimos soluta non vel. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Aut mollitia pariatur perferendis placeat dolores. + id: + type: string + description: Brief UUID + example: Sint nisi sunt sed. + keywords: + description: Keyword list + example: Provident rerum et voluptas tenetur esse. + platforms: + type: array + items: + type: string + example: Qui pariatur odio reprehenderit nam. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Et id sint suscipit quia ipsa voluptas. + - Non neque optio. + - Architecto molestiae cum. + - Perspiciatis consequatur beatae sunt officiis sequi qui. + program_type: + type: string + description: Funnel context + example: membership + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Dolor eaque culpa quae eius omnis id. + status: + type: string + description: Lifecycle status + example: approved + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Assumenda sed minima nihil. + url: + type: string + description: Event/course page URL + example: Dolore voluptatem qui eveniet amet. + version: + type: integer + description: Optimistic-concurrency version + example: 9087350093442770114 + format: int64 + example: + copy: Dolor cumque dolores. + event_details: Ad et in voluptatem et perspiciatis. + event_slug: Quia aut impedit a veritatis. + id: Voluptatem vel dolor molestiae quia voluptatem. + keywords: Aut ratione dicta. + platforms: + - Qui voluptas veniam. + - Voluptas maxime non illo ab in voluptatem. + - Est magnam molestiae soluta. + program_type: education + project_id: Libero qui. + status: draft + targeting: Aspernatur culpa sed non qui ad. + url: Consequuntur sed ea sunt debitis. + version: 1905073879386313651 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief3: + type: object + properties: + copy: + description: Ad copy + example: Alias quis magni ad doloremque. + event_details: + description: Extracted event/course details + example: Facere iste. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Reprehenderit saepe quas commodi qui qui mollitia. + id: + type: string + description: Brief UUID + example: Facere rerum voluptatem saepe eligendi. + keywords: + description: Keyword list + example: Et quam totam. + platforms: + type: array + items: + type: string + example: Reiciendis numquam consequatur. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Qui eaque ut iste. + - Aspernatur delectus rem. + program_type: + type: string + description: Funnel context + example: education + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Non minima possimus totam occaecati enim eaque. + status: + type: string + description: Lifecycle status + example: archived + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Eos repudiandae voluptate est enim aut laborum. + url: + type: string + description: Event/course page URL + example: Recusandae voluptates. + version: + type: integer + description: Optimistic-concurrency version + example: 7978828505057037537 + format: int64 + example: + copy: Quae qui optio magni itaque iure. + event_details: Placeat dolores culpa sed. + event_slug: Autem ullam. + id: Est et beatae. + keywords: Autem aperiam quis. + platforms: + - Eveniet explicabo voluptatem et quis. + - Ea ut voluptatem velit. + - Aut est est eum ut ut. + - Et molestiae blanditiis nihil dolor exercitationem. + program_type: events + project_id: Est officiis enim qui. + status: archived + targeting: Enim odit numquam. + url: Quis sed consequatur velit atque nam. + version: 4992739119008200883 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief4: + type: object + properties: + copy: + description: Ad copy + example: Dolor eum et consequatur. + event_details: + description: Extracted event/course details + example: Quae adipisci doloribus ullam. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Dignissimos aut eaque. + id: + type: string + description: Brief UUID + example: Dolores at voluptatem beatae quis dignissimos. + keywords: + description: Keyword list + example: Optio enim nisi doloremque rerum sed consequatur. + platforms: + type: array + items: + type: string + example: Fuga ducimus dolorem ut. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Ad optio magnam sequi esse. + - Modi vel aut rerum. + - Ad minus vero assumenda cum nemo. + - Nobis assumenda aut dolorem ea veniam. + program_type: + type: string + description: Funnel context + example: events + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Et labore sit. + status: + type: string + description: Lifecycle status + example: approved + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Quia amet vero dolorum incidunt officia nihil. + url: + type: string + description: Event/course page URL + example: Autem ut unde dolore id a. + version: + type: integer + description: Optimistic-concurrency version + example: 2033029701447525509 + format: int64 + example: + copy: Corrupti sint molestiae occaecati est. + event_details: Nam eius nulla suscipit ullam. + event_slug: Sint consequuntur omnis distinctio possimus non aliquid. + id: Perferendis tempore eum laboriosam. + keywords: Labore est ut ratione nobis ut occaecati. + platforms: + - Est et est. + - Deleniti non in iure dolores et quis. + - Recusandae similique assumenda ut quia. + - Nesciunt minus natus non. + program_type: membership + project_id: Odio at magnam saepe incidunt. + status: approved + targeting: Minima veritatis sed impedit laborum. + url: Facilis neque sed alias dolore id. + version: 7666233696583259840 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + Brief5: + type: object + properties: + copy: + description: Ad copy + example: Consequatur laborum. + event_details: + description: Extracted event/course details + example: Cum ipsa molestias voluptatibus nam debitis hic. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Et et iure tempora exercitationem voluptatum. + id: + type: string + description: Brief UUID + example: Ut qui sed explicabo dolorem totam non. + keywords: + description: Keyword list + example: Beatae non et excepturi sit minus. + platforms: + type: array + items: + type: string + example: Ut corrupti sunt labore. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Cum ipsum quo possimus. + - Quam culpa. + program_type: + type: string + description: Funnel context + example: events + enum: + - events + - education + - membership + project_id: + type: string + description: Owning project + example: Nesciunt sed magnam sed vero nulla. + status: + type: string + description: Lifecycle status + example: archived + enum: + - draft + - approved + - archived + targeting: + description: Targeting recommendation + example: Et quisquam dicta. + url: + type: string + description: Event/course page URL + example: Autem et. + version: + type: integer + description: Optimistic-concurrency version + example: 6074244296634719915 + format: int64 + example: + copy: Adipisci vel. + event_details: Et eum ex doloremque quia. + event_slug: Ipsam consequatur qui illo totam. + id: Est libero enim quo sit aut tempora. + keywords: Eum itaque. + platforms: + - Expedita commodi in dolores. + - Similique eaque autem impedit tenetur voluptatibus. + - Libero quia quam harum molestiae cupiditate. + program_type: membership + project_id: Neque assumenda sed. + status: draft + targeting: Recusandae recusandae est et fuga veniam natus. + url: Dignissimos in tempore quo consequatur sint qui. + version: 3604003677758724955 + required: + - id + - project_id + - program_type + - event_slug + - status + - version + BriefInput: + type: object + properties: + copy: + description: Ad copy + example: Praesentium et. + event_details: + description: Extracted event/course details + example: Dolor consectetur eos iste. + event_slug: + type: string + description: Event/course slug (unique within the project) + example: Porro commodi odio sunt esse. + keywords: + description: Keyword list + example: Veniam iusto consequatur quia sed officia eligendi. + platforms: + type: array + items: + type: string + example: Itaque voluptatem est quis impedit. + description: Suggested default platforms (a planning hint; binding selection is on the campaign) + example: + - Harum et consequatur eligendi eum necessitatibus. + - Magnam ut. + - Voluptate voluptates corrupti sapiente. + - Dolorum repellat expedita ut recusandae dolor. + program_type: + type: string + description: Funnel context + example: events + enum: + - events + - education + - membership + targeting: + description: Targeting recommendation + example: Qui impedit. + url: + type: string + description: Event/course page URL + example: Incidunt eos dolores laborum sed molestias. + example: + copy: Animi sit esse ratione aliquid. + event_details: Repudiandae tenetur aperiam saepe vel. + event_slug: Alias minus reprehenderit dignissimos aut velit. + keywords: Laborum est quasi dignissimos. + platforms: + - Omnis doloribus. + - Cum ut quo. + - Necessitatibus id sint dicta assumenda omnis voluptatem. + - Praesentium voluptatem blanditiis non totam temporibus. + program_type: events + targeting: Velit quis harum recusandae est ipsum. + url: At sint laborum veniam. + required: + - program_type + - event_slug + Campaign: + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Qui mollitia et eum veniam. + campaign_name: + type: string + description: Campaign name + example: Culpa sed minima iure. + etag: + type: string + description: ETag header value (mirrors version) + example: Et tempora quaerat et eius. + id: + type: string + description: Campaign UUID + example: Sint qui alias voluptatem ut. + platform: + type: string + description: Channel + example: Aliquam eaque voluptatem omnis. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Cupiditate aut. + project_id: + type: string + description: Owning project + example: Ut recusandae modi. + status: + type: string + description: Campaign status + example: Enim occaecati beatae eveniet molestiae ad voluptatum. + version: + type: integer + description: Optimistic-concurrency version + example: 1755263284665276006 + format: int64 + example: + brief_id: Et asperiores. + campaign_name: Voluptatem aut debitis vitae unde velit eos. + etag: Et et itaque qui repudiandae magnam. + id: Magni illum consequatur quaerat soluta. + platform: Maiores reiciendis. + platform_campaign_id: Dolor ipsam recusandae maxime nesciunt repellendus. + project_id: Vitae autem in. + status: Accusantium odit rerum commodi ea. + version: 3660260905327304928 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + Campaign2: + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Velit at omnis autem. + campaign_name: + type: string + description: Campaign name + example: Laboriosam et. + id: + type: string + description: Campaign UUID + example: Doloremque odio. + platform: + type: string + description: Channel + example: Ratione est omnis voluptas praesentium. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Aspernatur voluptatum facilis. + project_id: + type: string + description: Owning project + example: Ipsum expedita animi. + status: + type: string + description: Campaign status + example: Sapiente alias et error nobis minima. + version: + type: integer + description: Optimistic-concurrency version + example: 4893105039439918924 + format: int64 + example: + brief_id: Tempora rem ab earum debitis rerum perspiciatis. + campaign_name: Voluptas itaque quo soluta maxime temporibus earum. + id: Dolorem impedit qui. + platform: Pariatur quae amet officia. + platform_campaign_id: Quam nostrum. + project_id: Ipsa ut animi fuga. + status: Aperiam et ab. + version: 8291275966671335223 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + Campaign3: + type: object + properties: + brief_id: + type: string + description: Parent brief + example: Aut est et. + campaign_name: + type: string + description: Campaign name + example: Consequatur nemo molestiae modi ut non atque. + id: + type: string + description: Campaign UUID + example: Vel quia cum quas. + platform: + type: string + description: Channel + example: Debitis ea molestiae eum voluptatum. + platform_campaign_id: + type: string + description: ID returned by the ad platform + example: Illo eveniet. + project_id: + type: string + description: Owning project + example: Minus sit necessitatibus. + status: + type: string + description: Campaign status + example: Sed commodi voluptatibus. + version: + type: integer + description: Optimistic-concurrency version + example: 5825805897083964981 + format: int64 + example: + brief_id: Perspiciatis optio. + campaign_name: Excepturi cumque ullam esse. + id: Est fugit ratione consectetur. + platform: Quia doloribus omnis et excepturi fugit dolores. + platform_campaign_id: Quia voluptates voluptates nisi nihil aut molestias. + project_id: Sed fuga facilis asperiores enim nisi. + status: Non autem voluptatem ut. + version: 7716950937642575525 + required: + - id + - project_id + - brief_id + - platform + - campaign_name + - status + - version + CampaignCreateInput: type: object properties: - code: + config: + description: Per-platform campaign configuration + example: Omnis dolorem est aut quia recusandae. + platforms: + type: array + items: + type: string + example: hubspot + enum: + - google-ads + - linkedin-ads + - meta-ads + - reddit-ads + - twitter-ads + - microsoft-ads + - hubspot + description: Platforms to create campaigns on (binding selection) + example: + - google-ads + minItems: 1 + example: + config: Totam tempora vitae. + platforms: + - reddit-ads + required: + - platforms + CampaignUpdateInput: + type: object + properties: + campaign_name: type: string - description: HTTP status code - example: "400" - message: + description: Campaign name + example: Ullam qui rerum numquam quo. + config: + description: Campaign configuration snapshot + example: Quaerat molestias. + status: type: string - description: Error message - example: The request was invalid. + description: Campaign status + example: A quia magnam culpa ipsam totam. example: - code: "400" - message: The request was invalid. + campaign_name: Sequi aut veniam ratione. + config: Laboriosam cumque mollitia. + status: Perferendis omnis ad voluptas esse. required: - - code - - message + - campaign_name + - status ConflictError: type: object properties: @@ -3241,16 +4982,51 @@ components: message: type: string description: Human-readable detail - example: Rerum sunt eum et qui. + example: Et enim totam et minima. ok: type: boolean description: Whether the credential authenticated against the provider - example: true + example: false example: - message: Ut quia harum. + message: Est tempora qui. ok: false required: - ok + CreateBriefRequestBody: + type: object + properties: + brief: + $ref: '#/components/schemas/BriefInput' + example: + brief: + copy: Autem veniam. + event_details: Vel autem voluptates. + event_slug: Et est laboriosam. + keywords: Enim unde omnis. + platforms: + - Nam exercitationem consectetur sint. + - Necessitatibus ex ea ullam corrupti. + - Et omnis accusantium. + - Iure modi asperiores ut. + program_type: education + targeting: Quam sed rem quia consequuntur ut. + url: Facere sint totam sapiente vitae numquam ut. + required: + - brief + CreateCampaignsRequestBody: + type: object + properties: + input: + $ref: '#/components/schemas/CampaignCreateInput' + example: + input: + config: Quia quos ut tempora consequuntur cumque repudiandae. + platforms: + - microsoft-ads + - microsoft-ads + - linkedin-ads + required: + - input CreateGoogleAdsRequestBody: type: object properties: @@ -3264,10 +5040,10 @@ components: label: TLF Main login_customer_id: "9746983954" credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. required: - config - credentials @@ -3280,14 +5056,14 @@ components: $ref: '#/components/schemas/HubspotCredentials' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Vitae vel omnis autem accusamus mollitia. required: - config - credentials @@ -3301,10 +5077,10 @@ components: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Temporibus et provident maiores neque. org_id: "208777" credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Illo minima eaque earum. required: - config - credentials @@ -3318,12 +5094,12 @@ components: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. required: - config - credentials @@ -3336,14 +5112,14 @@ components: $ref: '#/components/schemas/MicrosoftAdsCredentials' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. credentials: - client_id: Voluptates odit consequatur quis. - client_secret: Est voluptatum enim earum et doloribus. - developer_token: Id ducimus eius et laboriosam omnis vitae. - refresh_token: Ea consequatur et ut. + client_id: Officiis aut fugiat optio ut. + client_secret: Et voluptas tenetur nobis facere. + developer_token: Blanditiis mollitia dolores illo nostrum magnam. + refresh_token: Quo corporis recusandae dicta quibusdam. required: - config - credentials @@ -3357,11 +5133,11 @@ components: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Laboriosam qui id enim. credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. required: - config - credentials @@ -3375,13 +5151,13 @@ components: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. required: - config - credentials @@ -3391,35 +5167,35 @@ components: account_id: type: string description: Provider account identifier - example: Voluptatem sed quia excepturi. + example: Voluptatem repellat expedita inventore. etag: type: string description: ETag header value (mirrors version) - example: Voluptas ullam dolores rem. + example: In doloremque autem maxime hic aliquam ratione. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Labore laudantium. + example: Odit provident sed quo aperiam. label: type: string description: Optional friendly name - example: Eos voluptatibus. + example: Delectus harum voluptates sit est corporis. login_customer_id: type: string description: Manager account used for API access - example: Officia quidem non qui voluptates natus cum. + example: Reprehenderit amet hic est odio. project_id: type: string description: Owning project - example: Facilis reiciendis dignissimos consequatur iste qui minima. + example: Aspernatur nihil et incidunt dolorem dolor sed. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -3428,18 +5204,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1885605882290356239 + example: 2066924184302318041 format: int64 example: - account_id: Iusto velit quo voluptatibus modi reiciendis aliquam. - etag: Sunt recusandae similique. + account_id: Ullam ipsum. + etag: Dolore sed. has_credentials: false - id: Autem recusandae totam. - label: Odit necessitatibus veritatis amet ut rerum quidem. - login_customer_id: Iusto quia. - project_id: Rerum nisi. + id: Occaecati rerum reiciendis. + label: Magnam voluptates molestiae libero autem quod. + login_customer_id: Sunt accusantium totam quia. + project_id: Esse mollitia et dolorum accusantium aspernatur quos. status: error - version: 4043658872882310311 + version: 3252068377050447352 required: - id - project_id @@ -3454,7 +5230,7 @@ components: account_id: type: string description: Provider account identifier - example: Error ipsam maiores et maiores laudantium aut. + example: Corporis et iste impedit voluptas. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3462,19 +5238,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Aliquid harum quis. + example: Non recusandae et qui ut exercitationem officia. label: type: string description: Optional friendly name - example: Mollitia odio et dolor et consectetur. + example: Unde quia ipsa sint optio nobis quae. login_customer_id: type: string description: Manager account used for API access - example: Sint inventore iste iure ut. + example: Accusamus provident omnis. project_id: type: string description: Owning project - example: Rerum eaque eius est rerum. + example: Tempora ea laboriosam. status: type: string description: Connection status @@ -3487,17 +5263,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1681601347963415661 + example: 3989869738197030603 format: int64 example: - account_id: Ipsa et dolore. - has_credentials: true - id: Facilis suscipit. - label: Ad inventore voluptatum eligendi quasi. - login_customer_id: Placeat unde distinctio ducimus qui. - project_id: Porro voluptate. - status: inactive - version: 1820304569870851201 + account_id: Repellendus saepe voluptatem dolores. + has_credentials: false + id: Eos earum fuga. + label: Reiciendis similique laborum tempora qui incidunt. + login_customer_id: Voluptates soluta animi sit ut. + project_id: Est facilis. + status: deleted + version: 3133980880975603750 required: - id - project_id @@ -3511,7 +5287,7 @@ components: account_id: type: string description: Provider account identifier - example: Nihil corrupti repellat eius. + example: Earum temporibus ipsum necessitatibus ratione possimus eum. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3519,23 +5295,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Dignissimos non sed omnis officiis et. + example: Voluptas incidunt eligendi consequatur. label: type: string description: Optional friendly name - example: Magni repudiandae. + example: Illo repellat et cum. login_customer_id: type: string description: Manager account used for API access - example: Delectus quam et eos. + example: Consequatur consequatur. project_id: type: string description: Owning project - example: Laboriosam qui id enim. + example: Labore eos et repudiandae odit omnis cupiditate. status: type: string description: Connection status - example: active + example: deleted enum: - active - inactive @@ -3544,17 +5320,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 8134511901185890068 + example: 2647723585259991483 format: int64 example: - account_id: Illo vitae occaecati omnis. + account_id: Quaerat iure ipsam. has_credentials: false - id: Rem aut fuga. - label: Cupiditate ut illo dolor quaerat totam. - login_customer_id: Explicabo possimus consequatur. - project_id: Est dolor ut corporis. + id: Et vel molestiae quo. + label: Et sed debitis. + login_customer_id: Aut minima adipisci aut quia quisquam veniam. + project_id: Placeat reprehenderit natus pariatur. status: active - version: 299946558233755685 + version: 7897706105846459095 required: - id - project_id @@ -3568,7 +5344,7 @@ components: account_id: type: string description: Provider account identifier - example: A necessitatibus molestiae. + example: Molestiae cupiditate quis modi blanditiis. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3576,23 +5352,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Quos hic doloribus. + example: Facilis sit autem. label: type: string description: Optional friendly name - example: Eum et sint corrupti eos. + example: Accusantium expedita alias placeat incidunt sit perspiciatis. login_customer_id: type: string description: Manager account used for API access - example: Dolorem qui explicabo consequuntur repellat non suscipit. + example: Magni sit harum. project_id: type: string description: Owning project - example: Neque autem ex eos. + example: Nihil qui doloremque nam magnam aliquid impedit. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -3601,17 +5377,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7747776820204091337 + example: 7345260181453091454 format: int64 example: - account_id: Sit est dolores est et. - has_credentials: false - id: Sit tempore. - label: Ut quo iure. - login_customer_id: Velit atque laudantium ullam rerum. - project_id: Magnam ut similique ea minima magnam. - status: deleted - version: 7309957715046801903 + account_id: Repellendus adipisci sit et enim quasi ut. + has_credentials: true + id: Et distinctio dignissimos fugiat voluptatibus natus. + label: Dolorem omnis aspernatur veritatis adipisci. + login_customer_id: Amet sed ipsam enim esse. + project_id: Commodi et nihil et. + status: active + version: 2256303273039083512 required: - id - project_id @@ -3646,25 +5422,25 @@ components: client_id: type: string description: OAuth client id - example: Ut aut. + example: In praesentium cumque. client_secret: type: string description: OAuth client secret - example: Sit similique ut minima ut. + example: Pariatur odio molestias voluptatum. developer_token: type: string description: Google Ads developer token - example: Voluptatum officia quod error amet quia inventore. + example: Nulla perferendis possimus deserunt iure et. refresh_token: type: string description: OAuth refresh token - example: Voluptas expedita omnis ex. + example: Voluptas necessitatibus cumque non porro qui molestias. description: Google Ads OAuth credential set. Write-only; never returned. example: - client_id: Non ut voluptas et accusamus natus id. - client_secret: Et officia sequi et quam. - developer_token: Vero quisquam. - refresh_token: Quia enim illo et eligendi. + client_id: Qui omnis nesciunt qui consequuntur. + client_secret: Fuga atque. + developer_token: Sequi culpa. + refresh_token: Ea quidem ducimus tempora ea molestias. required: - refresh_token - client_id @@ -3676,15 +5452,15 @@ components: account_id: type: string description: Provider account identifier - example: Delectus voluptatem iste nisi in. + example: Beatae totam. brand_kit: type: string description: Per-project brand kit selector - example: Nostrum corporis quasi magnam. + example: Molestiae veniam exercitationem blanditiis ratione non. etag: type: string description: ETag header value (mirrors version) - example: Dolor occaecati earum iusto rerum sit cupiditate. + example: Voluptate nam iste architecto. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3692,31 +5468,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Nam quia aperiam sunt est. + example: Eos ex laborum et doloremque. label: type: string description: Optional friendly name - example: Natus expedita aspernatur consequuntur quod ducimus molestias. + example: Quo est. portal_id: type: string description: HubSpot portal/account ID - example: Iste qui rerum est sed sed similique. + example: Velit odio temporibus aut numquam est. project_id: type: string description: Owning project - example: Libero ut est rerum est occaecati et. + example: Non laudantium quis ab non sunt. sender_email: type: string description: Default sender address - example: Ea maiores quidem molestiae deleniti omnis. + example: Ut vitae dolor soluta beatae. sender_name: type: string description: Default sender name - example: Expedita voluptatum commodi fuga ipsa. + example: Enim velit voluptates officiis tempora possimus rerum. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -3725,21 +5501,21 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3681253484013323435 + example: 7472986980795124202 format: int64 example: - account_id: Neque facilis est qui possimus eligendi. - brand_kit: Voluptas eius error autem est est distinctio. - etag: Et nobis voluptas dignissimos exercitationem illum. + account_id: Quos provident. + brand_kit: Dolorem ea et quidem quibusdam officiis. + etag: Officiis modi quae consequatur voluptatem et. has_credentials: false - id: Qui quidem exercitationem. - label: Sed numquam necessitatibus laudantium est ut. - portal_id: Quisquam ex est facilis qui ducimus. - project_id: Ipsa quo vel architecto quas eligendi impedit. - sender_email: Ut culpa excepturi. - sender_name: Officiis quia quisquam corrupti. + id: Sed dolorum cupiditate quia qui dicta. + label: Totam et voluptates atque consequatur consequuntur voluptates. + portal_id: Culpa veritatis ut adipisci minus. + project_id: Commodi facere vel. + sender_email: Vero eum ea perspiciatis rerum. + sender_name: Incidunt deleniti. status: inactive - version: 8080806383587730069 + version: 413153877019026133 required: - id - project_id @@ -3754,11 +5530,11 @@ components: account_id: type: string description: Provider account identifier - example: Magni omnis. + example: Ut illum laboriosam debitis aut. brand_kit: type: string description: Per-project brand kit selector - example: Perferendis culpa voluptatem nostrum eaque deleniti. + example: Quisquam sit ut aliquid consectetur. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3766,31 +5542,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Esse voluptate. + example: Et eveniet sunt. label: type: string description: Optional friendly name - example: Voluptatum et impedit quod. + example: Similique inventore et voluptatum. portal_id: type: string description: HubSpot portal/account ID - example: Est repellat. + example: Nobis ipsam blanditiis commodi accusamus maxime. project_id: type: string description: Owning project - example: Consectetur et culpa quaerat vel. + example: Veniam ut et sit. sender_email: type: string description: Default sender address - example: Esse sit id incidunt mollitia non sed. + example: Quia sed eum ad sunt impedit sint. sender_name: type: string description: Default sender name - example: Minima est distinctio accusamus. + example: Iusto aspernatur fugit et facere eligendi. status: type: string description: Connection status - example: inactive + example: deleted enum: - active - inactive @@ -3799,20 +5575,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7333781901734102627 + example: 3458229418861381091 format: int64 example: - account_id: Aliquam animi. - brand_kit: Eos iusto fugit. - has_credentials: true - id: Quo nostrum quia. - label: Velit quis. - portal_id: Error dolor placeat et. - project_id: Ut nobis. - sender_email: Et esse fugiat aliquid aspernatur voluptatem in. - sender_name: Consequatur et dolorem. + account_id: Fuga et id voluptatem et et et. + brand_kit: Velit architecto error consequatur nam. + has_credentials: false + id: Harum nisi illo quia. + label: Aliquam vel cum alias iusto. + portal_id: Doloribus ex dolor quia et dolores. + project_id: Aut in voluptate quam delectus optio saepe. + sender_email: Distinctio ea. + sender_name: Ab iste earum consequatur laboriosam maxime nemo. status: error - version: 2888074467672743464 + version: 6330333226367628566 required: - id - project_id @@ -3826,11 +5602,11 @@ components: account_id: type: string description: Provider account identifier - example: Voluptas omnis quidem dolor ipsum voluptas sed. + example: Reiciendis voluptas. brand_kit: type: string description: Per-project brand kit selector - example: Tempora dolor quis totam hic quibusdam eveniet. + example: Eligendi facere. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -3838,31 +5614,31 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Asperiores quidem. + example: Ipsum est a. label: type: string description: Optional friendly name - example: Nesciunt adipisci odit corrupti laudantium consequatur hic. + example: Et corporis error eligendi voluptatem repellat consectetur. portal_id: type: string description: HubSpot portal/account ID - example: Dolorem necessitatibus neque consectetur occaecati earum. + example: Nostrum quod rerum dolorum. project_id: type: string description: Owning project - example: Est nostrum atque. + example: Quia voluptatem veritatis molestiae similique. sender_email: type: string description: Default sender address - example: Ut sint non eum expedita non. + example: Doloribus et ex enim rem modi omnis. sender_name: type: string description: Default sender name - example: Sit voluptatem ad ab. + example: Atque quo sit iure quos. status: type: string description: Connection status - example: deleted + example: active enum: - active - inactive @@ -3871,20 +5647,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3980984224261423037 + example: 1162295855624220314 format: int64 example: - account_id: Quidem omnis totam eos non molestias. - brand_kit: Ducimus id sint eius aut. - has_credentials: false - id: Doloribus voluptas mollitia doloremque totam. - label: Quia doloremque commodi rerum. - portal_id: Recusandae illum accusamus debitis. - project_id: Veniam et ducimus quos amet. - sender_email: Praesentium sint laudantium error. - sender_name: Nihil iure officiis nemo quia. - status: inactive - version: 6150421422111226538 + account_id: Vitae sapiente dolor unde harum dolorem iusto. + brand_kit: Quis inventore iste nemo rerum corporis repellat. + has_credentials: true + id: Quo eveniet fuga quaerat. + label: Deserunt eligendi rerum architecto necessitatibus alias. + portal_id: Qui repellendus sit incidunt in et. + project_id: Et modi eveniet magni repellendus et. + sender_email: Et nesciunt et culpa similique quasi ratione. + sender_name: Laboriosam dolorem ut dolorem odio aut reprehenderit. + status: active + version: 7214923908250326040 required: - id - project_id @@ -3898,43 +5674,43 @@ components: account_id: type: string description: Provider account identifier - example: Ab quo ullam ad. + example: Et eveniet eaque non architecto aut. brand_kit: type: string description: Per-project brand kit selector - example: Aperiam facere voluptas quis nihil. + example: Nihil odio facere magnam quasi dolorem. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Et quia ex et alias ea. + example: Excepturi iusto in. label: type: string description: Optional friendly name - example: Laborum maiores est ea ducimus ipsum dolorem. + example: Rem aliquam voluptatem. portal_id: type: string description: HubSpot portal/account ID - example: Placeat explicabo. + example: Enim distinctio et corrupti magnam deleniti nam. project_id: type: string description: Owning project - example: Quas quis corporis. + example: Velit in. sender_email: type: string description: Default sender address - example: Rerum tenetur ut voluptatem quo. + example: Esse temporibus eaque ut voluptatibus. sender_name: type: string description: Default sender name - example: Iste aut cum voluptatem qui et. + example: Qui officia tempora alias quis. status: type: string description: Connection status - example: inactive + example: error enum: - active - inactive @@ -3943,20 +5719,20 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3448886829732168782 + example: 4048071747606254476 format: int64 example: - account_id: Vero ut et neque soluta distinctio voluptas. - brand_kit: Voluptatem incidunt cum. + account_id: Voluptatum nemo adipisci. + brand_kit: Optio enim repellendus commodi quia quod sit. has_credentials: false - id: Rem asperiores eligendi vero. - label: Facere quas qui aut rerum. - portal_id: Fugit eum. - project_id: Voluptatem fugiat et. - sender_email: Necessitatibus voluptatem incidunt et totam esse rerum. - sender_name: Dolorem delectus. - status: active - version: 5717670313645740058 + id: Sint consequatur quisquam nihil. + label: Laboriosam placeat nesciunt tempore. + portal_id: Molestias aliquid dolorum consequatur est qui qui. + project_id: Accusamus illum in delectus magnam corporis velit. + sender_email: Enim aut. + sender_name: Dolores sed. + status: deleted + version: 1989293428795722898 required: - id - project_id @@ -3970,34 +5746,34 @@ components: account_id: type: string description: HubSpot list/audience ID - example: Quis quis reprehenderit non quae omnis qui. + example: Unde perspiciatis. brand_kit: type: string description: Per-project brand kit selector - example: Voluptatibus deserunt. + example: Assumenda quam possimus aut ipsa. label: type: string description: Optional friendly name - example: At odio incidunt distinctio sint qui. + example: Sed qui dolor illo facere ut voluptatem. portal_id: type: string description: HubSpot portal/account ID - example: Cumque saepe sint quidem odio ab. + example: Rerum maiores in alias est laboriosam saepe. sender_email: type: string description: Default sender address - example: Aut est. + example: Perferendis placeat earum occaecati et. sender_name: type: string description: Default sender name - example: Error pariatur consequatur esse. + example: Eaque magni omnis dolorem. example: - account_id: Et eligendi et numquam dolorum consequatur in. - brand_kit: Non sequi error assumenda quam deleniti et. - label: Necessitatibus eos. - portal_id: Et eum est molestiae necessitatibus magni. - sender_email: Molestiae praesentium perferendis deserunt ipsum dolorum. - sender_name: Error nulla fuga fugit repellendus et accusamus. + account_id: Et natus possimus cupiditate. + brand_kit: Quod amet itaque aut. + label: Quae et perspiciatis numquam. + portal_id: Molestiae quia neque atque delectus. + sender_email: Repudiandae eum. + sender_name: Ut itaque voluptatem unde adipisci aut. required: - account_id HubspotCredentials: @@ -4006,10 +5782,10 @@ components: private_app_token: type: string description: HubSpot private app token - example: Dolor officia eligendi. + example: Aut rem molestiae assumenda ea. description: HubSpot private app token. Write-only; never returned. example: - private_app_token: Provident dicta temporibus debitis. + private_app_token: Nam repellat. required: - private_app_token InternalServerError: @@ -4029,17 +5805,114 @@ components: required: - code - message + JobCreateResponse: + type: object + properties: + job_id: + type: string + description: Poll GET /projects/{projectId}/jobs/{jobId} + example: Consequuntur esse culpa. + platforms: + type: array + items: + type: string + example: Ut ullam aliquam dolore iure consequatur. + description: Platforms this job will create on + example: + - Iste nemo. + - Sunt deleniti. + - Natus quia labore non ut dolores et. + status: + type: string + description: Initial status (always 'queued' on create) + example: queued + enum: + - queued + example: + job_id: Voluptatem ipsa. + platforms: + - Ipsam tempora est. + - Sequi ea ut vero consequatur possimus. + - Assumenda sit delectus veniam. + status: queued + required: + - job_id + - status + - platforms + JobPollResponse: + type: object + properties: + error: + type: string + description: Terminal error, if any + example: Temporibus illum impedit ullam eaque. + job_id: + type: string + description: Job UUID + example: Sunt quis odio enim nam id voluptatem. + result: + type: array + items: + $ref: '#/components/schemas/PlatformResult' + description: Per-platform results, written once when the job reaches a terminal state + example: + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + status: + type: string + description: Job status + example: queued + enum: + - queued + - running + - succeeded + - partial + - failed + example: + error: Itaque optio consequatur. + job_id: Harum sint neque quia. + result: + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + - campaign_id: Aliquam facilis iste sed voluptatum laboriosam. + error: Vero voluptatum a quos a sed exercitationem. + ok: false + platform: Ullam enim voluptatem. + status: failed + required: + - job_id + - status LinkedinAdsConnection: type: object properties: account_id: type: string description: Provider account identifier - example: Cumque molestiae eos rem aut. + example: Laboriosam quos omnis deserunt libero consequatur quisquam. etag: type: string description: ETag header value (mirrors version) - example: Non adipisci rerum. + example: Modi cupiditate. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4047,23 +5920,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Adipisci quaerat est nobis. + example: Soluta quod. label: type: string description: Optional friendly name - example: Et veritatis quisquam sed ratione et. + example: Omnis inventore voluptatum ab. org_id: type: string description: LinkedIn organization URN id - example: Placeat ea odio ea eos. + example: Ducimus qui modi consequatur. project_id: type: string description: Owning project - example: Fugit non fuga rem adipisci. + example: At fugiat. status: type: string description: Connection status - example: active + example: inactive enum: - active - inactive @@ -4072,18 +5945,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7868855978793741277 + example: 7336458220235154606 format: int64 example: - account_id: Nobis itaque est enim magnam pariatur. - etag: Praesentium ipsam officiis cum ipsum recusandae. + account_id: Aliquam et asperiores quae voluptatem velit. + etag: Enim voluptas numquam. has_credentials: true - id: Ipsum et delectus dolorum eveniet. - label: Maxime maxime porro enim. - org_id: Adipisci ullam pariatur ex voluptas. - project_id: Alias ea quibusdam. - status: active - version: 5465298508990820984 + id: Et et deleniti sint reiciendis. + label: Hic quam impedit placeat consequatur. + org_id: Et iusto eius dolor magnam necessitatibus. + project_id: Eos et quidem est. + status: error + version: 5408757854107547795 required: - id - project_id @@ -4098,7 +5971,7 @@ components: account_id: type: string description: Provider account identifier - example: Dolorum deserunt. + example: Adipisci omnis veritatis omnis praesentium omnis. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4106,19 +5979,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Rerum adipisci. + example: Est laborum. label: type: string description: Optional friendly name - example: Non laudantium sunt minima repudiandae illo harum. + example: Quod aut sunt ex et autem delectus. org_id: type: string description: LinkedIn organization URN id - example: Eos nihil ipsam ex odio totam. + example: Aut natus aliquam. project_id: type: string description: Owning project - example: Enim accusantium labore esse numquam. + example: Libero necessitatibus. status: type: string description: Connection status @@ -4131,17 +6004,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2440284349670140496 + example: 6869928491943758950 format: int64 example: - account_id: Omnis consequatur. + account_id: Tempore qui est. has_credentials: true - id: Deleniti placeat fuga aut saepe. - label: Ut quis ut accusantium. - org_id: Aut autem sint minus voluptatum. - project_id: Reiciendis pariatur repellat. - status: error - version: 4719926195195381744 + id: Quas aliquid. + label: Praesentium beatae quis ut eius. + org_id: Excepturi provident saepe aliquam architecto. + project_id: Saepe non tempore. + status: active + version: 8098284705619161180 required: - id - project_id @@ -4155,27 +6028,27 @@ components: account_id: type: string description: Provider account identifier - example: Quo veritatis quis delectus est. + example: Minus pariatur praesentium amet. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Tempore quae. + example: Vero provident voluptas consequatur iusto. label: type: string description: Optional friendly name - example: Dolores tenetur ut illum. + example: Ut magnam aut illum commodi placeat sed. org_id: type: string description: LinkedIn organization URN id - example: Expedita accusantium vel voluptate. + example: Qui sint velit earum laboriosam dignissimos reiciendis. project_id: type: string description: Owning project - example: Consequatur dolore unde illum. + example: Pariatur quas. status: type: string description: Connection status @@ -4188,17 +6061,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1676862666948614995 + example: 1671194498896693162 format: int64 example: - account_id: Magni consequatur dignissimos itaque quia. - has_credentials: true - id: Repellat assumenda nesciunt distinctio eaque aliquam. - label: Et reprehenderit dignissimos. - org_id: Et voluptatem deserunt consequuntur omnis sed dolore. - project_id: Veniam consectetur deserunt in hic dolorum facere. + account_id: Provident autem repudiandae molestias est consectetur porro. + has_credentials: false + id: Quisquam voluptas quasi esse corporis molestiae. + label: Dolorem totam voluptatem recusandae amet. + org_id: Ea ut. + project_id: Suscipit fugiat deserunt. status: inactive - version: 7072791787806691868 + version: 7812167479041835646 required: - id - project_id @@ -4212,27 +6085,27 @@ components: account_id: type: string description: Provider account identifier - example: Assumenda dignissimos beatae vitae aut ea dolor. + example: Et libero ea aut omnis. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Voluptatibus quod necessitatibus non iste ad rem. + example: Doloremque aut eligendi perferendis aut et. label: type: string description: Optional friendly name - example: Harum corporis deleniti doloribus. + example: Id officiis architecto. org_id: type: string description: LinkedIn organization URN id - example: Sit architecto ut unde. + example: Corrupti aut earum rerum ducimus dicta. project_id: type: string description: Owning project - example: Sunt qui cupiditate ullam ab suscipit et. + example: Eum molestiae ea consectetur delectus. status: type: string description: Connection status @@ -4245,17 +6118,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6055913800302101668 + example: 6409712151005637179 format: int64 example: - account_id: Praesentium tenetur quas at ut necessitatibus dolor. + account_id: Et rerum repudiandae et at iusto. has_credentials: false - id: Sapiente possimus. - label: Eaque voluptate reprehenderit sed voluptatem sed illo. - org_id: Nihil id unde veritatis officiis aut fugiat. - project_id: Voluptates atque. - status: error - version: 446959638978499075 + id: Quo omnis et et molestiae dolorum. + label: Consequatur iste eum amet vel impedit et. + org_id: Veritatis saepe numquam rem sapiente. + project_id: Perspiciatis a aut quas delectus a. + status: inactive + version: 8814877888903559420 required: - id - project_id @@ -4273,14 +6146,14 @@ components: label: type: string description: Optional friendly name - example: Cumque totam nulla aut reprehenderit. + example: Est ipsam sint autem repudiandae asperiores et. org_id: type: string description: LinkedIn organization URN id example: "208777" example: account_id: "538170226" - label: Illum rerum error incidunt. + label: Quia consectetur non nulla dolore quibusdam ducimus. org_id: "208777" required: - account_id @@ -4291,10 +6164,10 @@ components: access_token: type: string description: OAuth access token - example: Aspernatur possimus. + example: Accusantium debitis adipisci qui iste explicabo. description: LinkedIn Ads credential. Write-only; never returned. example: - access_token: Repellendus quo quidem. + access_token: Commodi qui. required: - access_token MetaAdsConnection: @@ -4303,15 +6176,15 @@ components: account_id: type: string description: Provider account identifier - example: Iusto ad animi eos doloribus autem corrupti. + example: Ut magni beatae error deleniti. app_id: type: string description: Meta app ID - example: Laborum eum et quidem. + example: Enim non asperiores velit aut. etag: type: string description: ETag header value (mirrors version) - example: Suscipit quas sunt eos nulla. + example: Culpa aut temporibus dolorem. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4319,23 +6192,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Facere exercitationem et exercitationem. + example: Tenetur nobis qui. label: type: string description: Optional friendly name - example: Rerum quidem accusamus et provident provident omnis. + example: Inventore consequatur velit autem eaque eaque. page_id: type: string description: Facebook page ID - example: Perferendis qui maiores aut reiciendis. + example: Quis accusantium. project_id: type: string description: Owning project - example: Eaque deleniti. + example: Laboriosam ipsa voluptas et vel dolorum. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -4344,19 +6217,19 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1750381436080099913 + example: 7112569565459851125 format: int64 example: - account_id: Ad non. - app_id: Est voluptas sequi. - etag: Possimus consequatur est unde. + account_id: Sit nihil enim sit. + app_id: Ipsam eligendi est dolore. + etag: Qui vitae iste. has_credentials: false - id: Quasi quis ab deserunt quia magnam. - label: Quod modi voluptas dolores commodi dicta. - page_id: Et laudantium. - project_id: Minus et aut consequatur non cumque. - status: active - version: 3589524561643771783 + id: Quod minima. + label: Dolores dolores. + page_id: Non ipsam optio in ratione accusamus quasi. + project_id: Ipsum et et laudantium. + status: deleted + version: 6379674624138849400 required: - id - project_id @@ -4371,35 +6244,35 @@ components: account_id: type: string description: Provider account identifier - example: Nostrum tempore. + example: Ullam sunt rem non doloribus. app_id: type: string description: Meta app ID - example: Rerum explicabo. + example: Consequuntur quia et saepe aperiam. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Ut commodi et voluptas tenetur nobis facere. + example: Esse qui quisquam. label: type: string description: Optional friendly name - example: Blanditiis mollitia dolores illo nostrum magnam. + example: Officiis asperiores quibusdam accusamus possimus. page_id: type: string description: Facebook page ID - example: Deserunt repellendus corrupti itaque quo sit. + example: Corrupti ipsum. project_id: type: string description: Owning project - example: Quo corporis recusandae dicta quibusdam. + example: Est magni temporibus et molestias voluptatem molestias. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -4408,18 +6281,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6534648276901202778 + example: 3056771457873327009 format: int64 example: - account_id: Perspiciatis porro. - app_id: Deserunt quod. + account_id: Aut quidem beatae officiis expedita error. + app_id: Corporis nulla magni. has_credentials: false - id: Eum et ipsum expedita reprehenderit eos. - label: Aperiam et nihil. - page_id: Non rem error. - project_id: Dolorum ut architecto deleniti nostrum. - status: deleted - version: 2783740423386270532 + id: Et illo aut natus. + label: Dolorem labore laboriosam repellat harum. + page_id: Debitis iusto molestias itaque. + project_id: Accusamus ipsa qui. + status: error + version: 5243837876383249590 required: - id - project_id @@ -4433,35 +6306,35 @@ components: account_id: type: string description: Provider account identifier - example: Voluptate necessitatibus enim et et itaque. + example: Consequatur provident ea sint. app_id: type: string description: Meta app ID - example: Nihil aut rerum est vel. + example: Minus id deleniti. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Aperiam quos perferendis nisi blanditiis eveniet. + example: Fugit et soluta. label: type: string description: Optional friendly name - example: Nobis omnis. + example: Voluptatem iste nemo numquam unde perspiciatis. page_id: type: string description: Facebook page ID - example: Rerum quis et modi optio labore omnis. + example: Facere ut repudiandae eum quibusdam. project_id: type: string description: Owning project - example: Ea enim ut corporis dolorum consequatur eius. + example: Et qui officiis repudiandae qui iste accusantium. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -4470,18 +6343,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 9014435567821091487 + example: 1953847576666852892 format: int64 example: - account_id: Dolor aut assumenda fugiat. - app_id: Aut quas suscipit eos. - has_credentials: true - id: Rem ab corporis. - label: Porro et. - page_id: Inventore alias error aut maxime dicta. - project_id: Non dolorem. - status: deleted - version: 5862072609653585869 + account_id: Eum autem quia quis soluta nostrum cumque. + app_id: Saepe amet voluptatem veniam. + has_credentials: false + id: Dolorem quia. + label: Adipisci consectetur ut modi quo et. + page_id: Rem autem quia velit aliquid. + project_id: Non id. + status: error + version: 7788869661064623160 required: - id - project_id @@ -4495,11 +6368,11 @@ components: account_id: type: string description: Provider account identifier - example: Dolor et corrupti voluptas neque vitae. + example: Eos fugiat rem minima ab ea animi. app_id: type: string description: Meta app ID - example: Rerum numquam amet deserunt rem quia. + example: Voluptatem libero consectetur. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4507,23 +6380,23 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Ipsa rerum velit et impedit. + example: Vero possimus rem maiores voluptas voluptate. label: type: string description: Optional friendly name - example: Velit quam omnis. + example: Eos qui facilis possimus consequatur sequi. page_id: type: string description: Facebook page ID - example: Mollitia est assumenda aut harum. + example: Facere ea incidunt et molestiae. project_id: type: string description: Owning project - example: Quaerat labore dolor. + example: Quam quos dolor id debitis. status: type: string description: Connection status - example: active + example: inactive enum: - active - inactive @@ -4532,18 +6405,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1514261665871912761 + example: 6530264541662515284 format: int64 example: - account_id: Deleniti deserunt labore. - app_id: Molestiae deserunt voluptates. + account_id: Minus alias laudantium. + app_id: Rerum numquam et ex aut cupiditate qui. has_credentials: true - id: Ipsam dolores et et qui ut. - label: Velit esse voluptate eum laboriosam distinctio. - page_id: Adipisci quo. - project_id: Numquam omnis ullam facilis ab sapiente. - status: error - version: 3954861542185537606 + id: Ducimus temporibus ullam sint aliquam ea voluptatibus. + label: Temporibus accusamus rerum. + page_id: Eius reiciendis rerum voluptatem. + project_id: Impedit incidunt sint. + status: inactive + version: 93554437328757363 required: - id - project_id @@ -4561,20 +6434,20 @@ components: app_id: type: string description: Meta app ID - example: Eius sed qui deserunt perspiciatis nihil. + example: Explicabo totam officia dolores. label: type: string description: Optional friendly name - example: Et eveniet. + example: Est et corporis maxime voluptate. page_id: type: string description: Facebook page ID - example: Corporis ut non. + example: Reprehenderit consectetur accusamus eaque inventore et. example: account_id: act_193556282970417 - app_id: Aut hic. - label: Mollitia tempora minima voluptas. - page_id: Facere aut facilis porro dolor ipsa. + app_id: Accusantium aut reiciendis in. + label: Reprehenderit aut ex dolor sint. + page_id: Rem nihil vitae qui explicabo. required: - account_id MetaAdsCredentials: @@ -4583,15 +6456,15 @@ components: access_token: type: string description: Meta access token - example: Aperiam voluptates. + example: Mollitia earum aut qui sit rerum. app_secret: type: string description: Meta app secret - example: Vero ducimus. + example: Tempora voluptatem rem fugit accusantium est necessitatibus. description: Meta Ads credential. Write-only; never returned. example: - access_token: Recusandae aut perferendis. - app_secret: Est eaque ipsam harum nisi nobis. + access_token: Officia dolorem. + app_secret: Asperiores autem aut aut soluta quae. required: - access_token - app_secret @@ -4601,35 +6474,35 @@ components: account_id: type: string description: Provider account identifier - example: Et nulla officia vitae. + example: In voluptate debitis earum voluptates repellat. customer_id: type: string description: Microsoft Advertising customer ID - example: Et sunt quis dicta voluptate aut in. + example: Dolore expedita. etag: type: string description: ETag header value (mirrors version) - example: Ut asperiores quo aut. + example: Dolorem tempore qui. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Perspiciatis id. + example: Necessitatibus quia facere vitae aperiam velit corrupti. label: type: string description: Optional friendly name - example: Nihil nihil quae porro qui rem autem. + example: Deserunt minima quis impedit. project_id: type: string description: Owning project - example: Ut non error aut et fuga. + example: Pariatur totam. status: type: string description: Connection status - example: inactive + example: deleted enum: - active - inactive @@ -4638,18 +6511,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2431299518347433245 + example: 3340961523623056274 format: int64 example: - account_id: Id temporibus et provident maiores neque quis. - customer_id: Sunt assumenda. - etag: Eligendi ut. - has_credentials: false - id: Provident nobis soluta culpa vitae. - label: Laudantium amet veritatis rerum itaque consequatur laboriosam. - project_id: Excepturi deleniti voluptas aperiam vel. - status: error - version: 4162198444433663334 + account_id: Molestias reprehenderit. + customer_id: Ea pariatur. + etag: Dolorem similique sapiente. + has_credentials: true + id: Sit omnis. + label: Accusantium est. + project_id: Porro saepe eius earum quo porro. + status: active + version: 5013574793121618962 required: - id - project_id @@ -4664,27 +6537,27 @@ components: account_id: type: string description: Provider account identifier - example: Earum minus autem omnis qui debitis eum. + example: Non rerum. customer_id: type: string description: Microsoft Advertising customer ID - example: Quidem necessitatibus voluptatem consequatur sit. + example: Tenetur magnam quaerat laborum voluptatibus accusamus itaque. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Esse officia explicabo dolor deserunt aspernatur. + example: Culpa est ex. label: type: string description: Optional friendly name - example: Cupiditate mollitia hic vel ut. + example: Eius eos sit quam fugiat. project_id: type: string description: Owning project - example: Aspernatur illo accusamus. + example: Aliquid porro deserunt nostrum sed sint voluptas. status: type: string description: Connection status @@ -4697,17 +6570,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3699306814781450025 + example: 396103720096053742 format: int64 example: - account_id: Sed corrupti sit. - customer_id: Consectetur tenetur eum. + account_id: Cumque adipisci. + customer_id: Error incidunt consequuntur voluptas ullam optio. has_credentials: true - id: Et vel beatae molestiae fuga. - label: Aut ut. - project_id: Vitae consequatur eos. - status: active - version: 436879921687162189 + id: Aut et rem eos quia. + label: Laboriosam placeat. + project_id: Modi accusantium et repudiandae facere dolorum. + status: error + version: 316921975293270952 required: - id - project_id @@ -4721,11 +6594,11 @@ components: account_id: type: string description: Provider account identifier - example: Qui impedit temporibus eveniet eos omnis cumque. + example: Reprehenderit nemo voluptatibus. customer_id: type: string description: Microsoft Advertising customer ID - example: Natus et unde. + example: Tempore aut voluptas ipsa quis animi. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4733,19 +6606,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Aut ut sit nisi architecto. + example: Eaque eos veniam quidem maiores adipisci. label: type: string description: Optional friendly name - example: Praesentium voluptate. + example: Molestias ullam aut odit repellat nihil at. project_id: type: string description: Owning project - example: Rerum et recusandae possimus quis. + example: Impedit a. status: type: string description: Connection status - example: inactive + example: error enum: - active - inactive @@ -4754,17 +6627,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 1288874109548033633 + example: 1334308724833154060 format: int64 example: - account_id: Hic est quia pariatur odit ullam. - customer_id: Sed quo perferendis nemo ut quisquam. + account_id: Ipsum et illum. + customer_id: Enim est sapiente. has_credentials: true - id: Explicabo repudiandae aut reiciendis recusandae molestias. - label: Quod impedit accusamus incidunt minus veritatis enim. - project_id: Vel inventore aliquid aut non eligendi accusamus. - status: error - version: 2175885598856983154 + id: Sunt reprehenderit aliquam perspiciatis libero. + label: Et qui quos corrupti quam unde. + project_id: Ratione pariatur. + status: inactive + version: 4456369783861517132 required: - id - project_id @@ -4778,11 +6651,11 @@ components: account_id: type: string description: Provider account identifier - example: Hic rerum accusamus velit error. + example: Quis ut in voluptatem nesciunt. customer_id: type: string description: Microsoft Advertising customer ID - example: Dolorem deserunt quasi in aut nam rerum. + example: Unde adipisci rerum eius vitae perferendis. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -4790,19 +6663,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Repellendus quos magnam. + example: Minus aut ut non. label: type: string description: Optional friendly name - example: Pariatur repellat vel. + example: Blanditiis est et saepe. project_id: type: string description: Owning project - example: In id voluptatem nihil quaerat. + example: Repellendus voluptatem. status: type: string description: Connection status - example: deleted + example: error enum: - active - inactive @@ -4811,17 +6684,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 8411140861799189617 + example: 4034090178991751772 format: int64 example: - account_id: Sint est sed. - customer_id: Illum quisquam ex ipsa. + account_id: Eum nemo architecto est. + customer_id: Consequatur provident veniam omnis quia. has_credentials: true - id: Distinctio magnam. - label: Quia magni aut nisi est. - project_id: Architecto deserunt vero ducimus distinctio. - status: active - version: 6452524533918462281 + id: Quidem eum animi. + label: Est error maiores excepturi exercitationem quo porro. + project_id: Explicabo et quo et velit qui doloremque. + status: deleted + version: 5942764375177239504 required: - id - project_id @@ -4835,19 +6708,19 @@ components: account_id: type: string description: Microsoft Advertising account ID - example: Ex quo praesentium voluptas. + example: Laudantium eligendi ut et. customer_id: type: string description: Microsoft Advertising customer ID - example: Est officia et. + example: Maiores nam sequi iste iusto reiciendis ipsa. label: type: string description: Optional friendly name - example: Voluptatem mollitia omnis doloribus qui consequatur et. + example: Est dolor velit et voluptatem mollitia placeat. example: - account_id: Eos et architecto voluptas. - customer_id: Vel natus provident. - label: Numquam accusamus. + account_id: Temporibus in qui provident corrupti ut similique. + customer_id: Voluptas doloremque nobis consectetur voluptas voluptas. + label: Saepe beatae. required: - account_id MicrosoftAdsCredentials: @@ -4856,25 +6729,25 @@ components: client_id: type: string description: OAuth client id - example: Architecto itaque doloribus voluptatem consequatur quis fugiat. + example: Beatae facilis dolor quod nihil quia. client_secret: type: string description: OAuth client secret - example: Recusandae qui culpa. + example: Sed voluptas fuga officia nulla. developer_token: type: string description: Microsoft Advertising developer token - example: Sed rerum dicta distinctio voluptas ut ut. + example: Non consequuntur qui qui impedit assumenda voluptas. refresh_token: type: string description: OAuth refresh token - example: Accusamus et atque enim. + example: Recusandae consequatur nulla. description: Microsoft Advertising OAuth credential set. Write-only; never returned. example: - client_id: Fuga nostrum velit hic excepturi omnis. - client_secret: Distinctio quos ut unde et atque. - developer_token: In recusandae maiores et voluptatem. - refresh_token: Odio labore pariatur nobis. + client_id: Consequuntur provident nostrum vel ut. + client_secret: Et nemo dicta illum omnis. + developer_token: Nemo cum delectus laudantium. + refresh_token: Dolor et aliquam deleniti. required: - client_id - client_secret @@ -4897,6 +6770,33 @@ components: required: - code - message + PlatformResult: + type: object + properties: + campaign_id: + type: string + description: Upstream platform campaign id. Present when ok; also set on the specific failure where the upstream campaign was created but recording it failed, so the orphaned id isn't lost. + example: Provident illo non quisquam iure. + error: + type: string + description: Failure reason (present when not ok) + example: Est ut aliquid. + ok: + type: boolean + description: Whether the campaign was created (or reused) successfully + example: true + platform: + type: string + description: Platform this result is for + example: Est et magni. + example: + campaign_id: Exercitationem accusamus pariatur saepe ab qui. + error: Ab laboriosam sunt quis omnis. + ok: false + platform: Repellendus eveniet veritatis impedit aut iste earum. + required: + - platform + - ok PreconditionFailedError: type: object properties: @@ -4937,31 +6837,31 @@ components: account_id: type: string description: Provider account identifier - example: Dolores assumenda accusantium mollitia fugiat dolore similique. + example: Sit soluta quia qui. etag: type: string description: ETag header value (mirrors version) - example: Molestiae quos cum. + example: Expedita sed velit distinctio. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Cum molestias fuga voluptatibus quis. + example: Optio quia similique vero est impedit quo. label: type: string description: Optional friendly name - example: Nobis aliquid rerum consequatur officiis velit. + example: Libero officia et quia. project_id: type: string description: Owning project - example: Aut rerum ut blanditiis dolor. + example: Nihil at. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -4970,17 +6870,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 4135215091203906464 + example: 1438953144952054594 format: int64 example: - account_id: Natus placeat esse est in deserunt aliquid. - etag: Sed et odio sunt totam incidunt. + account_id: Optio rerum vel molestiae id. + etag: Reiciendis excepturi. has_credentials: false - id: Architecto doloremque aperiam doloribus aut qui. - label: Distinctio ipsum ab. - project_id: Placeat cumque iure veritatis cumque nisi. - status: error - version: 6763438623066254599 + id: Necessitatibus eos rerum. + label: Qui quas aut provident suscipit. + project_id: Veniam amet et quisquam voluptatem fuga. + status: active + version: 8349066598252210000 required: - id - project_id @@ -4995,27 +6895,27 @@ components: account_id: type: string description: Provider account identifier - example: Assumenda harum eos eveniet ut et laboriosam. + example: Repellendus velit soluta est quidem in dolorem. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Culpa saepe necessitatibus. + example: Omnis vitae. label: type: string description: Optional friendly name - example: Voluptas aperiam. + example: Amet et vel ipsa ad doloremque qui. project_id: type: string description: Owning project - example: Dolores enim. + example: Voluptatem eum consequuntur. status: type: string description: Connection status - example: error + example: inactive enum: - active - inactive @@ -5024,16 +6924,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 6456486653687697700 + example: 7523183128313694126 format: int64 example: - account_id: Cumque quae. - has_credentials: false - id: Qui eaque ut laudantium. - label: Laborum aspernatur voluptate itaque fugit. - project_id: Ratione id. - status: deleted - version: 5238309974693293664 + account_id: Autem repellat et. + has_credentials: true + id: Accusamus eligendi itaque est est. + label: Nesciunt consequatur commodi. + project_id: Explicabo doloremque possimus soluta cupiditate ab. + status: active + version: 7930594330395664596 required: - id - project_id @@ -5047,7 +6947,7 @@ components: account_id: type: string description: Provider account identifier - example: Dolore velit pariatur non assumenda perspiciatis dolor. + example: At ratione unde dicta. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5055,19 +6955,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Odit consequatur. + example: Praesentium omnis amet quia quia ea amet. label: type: string description: Optional friendly name - example: Ut ullam. + example: At explicabo reiciendis. project_id: type: string description: Owning project - example: Vero soluta omnis eius. + example: Officia rerum eligendi ex. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -5076,16 +6976,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3837313741085790100 + example: 6548567568963242840 format: int64 example: - account_id: Animi molestiae aut fugit alias nisi qui. - has_credentials: false - id: Molestiae ea ullam minima voluptatem vel. - label: Beatae quia minus vero at ut. - project_id: Dolore repudiandae necessitatibus dolorem. + account_id: Aut esse sint earum magnam. + has_credentials: true + id: Ipsum pariatur explicabo. + label: Et perferendis aut voluptates voluptatem. + project_id: Qui voluptas. status: inactive - version: 8129543513726361746 + version: 7316195578965090635 required: - id - project_id @@ -5099,27 +6999,27 @@ components: account_id: type: string description: Provider account identifier - example: Autem iste ea in ipsa. + example: Corporis nemo et consectetur consequatur. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: Deserunt dolorum nihil. + example: Fuga natus. label: type: string description: Optional friendly name - example: Odio odio sed minus quia quia ea. + example: Eum voluptatem eveniet. project_id: type: string description: Owning project - example: Doloremque explicabo ut. + example: Est sed et ducimus qui quas. status: type: string description: Connection status - example: error + example: active enum: - active - inactive @@ -5128,16 +7028,16 @@ components: version: type: integer description: Optimistic-concurrency version - example: 4150548058954901958 + example: 8379899225513618277 format: int64 example: - account_id: Quibusdam consequuntur dolorem fugiat. - has_credentials: true - id: Perspiciatis magni tempore sunt itaque. - label: Sit beatae ut modi voluptatum veniam. - project_id: Ut ex. - status: error - version: 5881477690177318757 + account_id: Tempore ut. + has_credentials: false + id: Consectetur repudiandae hic nostrum. + label: Doloremque ad. + project_id: Sed est architecto sit est est labore. + status: active + version: 477213815092233328 required: - id - project_id @@ -5155,10 +7055,10 @@ components: label: type: string description: Optional friendly name - example: Qui molestias. + example: Dolores earum. example: account_id: t2_gv9wtbfa - label: Molestiae minus dolor accusantium. + label: Est consequatur rerum neque velit nulla modi. required: - account_id RedditAdsCredentials: @@ -5167,20 +7067,20 @@ components: client_id: type: string description: OAuth client id - example: Numquam quidem qui. + example: Consequatur aut vero. client_secret: type: string description: OAuth client secret - example: Laboriosam possimus dolor facere velit dolores. + example: Excepturi natus est est saepe doloribus unde. refresh_token: type: string description: OAuth refresh token - example: Alias ad voluptatem accusantium tempore impedit. + example: Aut perspiciatis. description: Reddit Ads OAuth credential. Write-only; never returned. example: - client_id: Et expedita itaque quos. - client_secret: Quos accusantium fugit. - refresh_token: Excepturi saepe sit vel perspiciatis neque. + client_id: Adipisci ut. + client_secret: Sit totam ut et. + refresh_token: Quibusdam ea nam aut. required: - client_id - client_secret @@ -5209,10 +7109,10 @@ components: $ref: '#/components/schemas/GoogleAdsCredentials' example: credentials: - client_id: Recusandae nam autem dicta perspiciatis quod. - client_secret: Quo voluptatem quas culpa omnis consequuntur iste. - developer_token: Id magnam id nihil fugiat. - refresh_token: Inventore quisquam laudantium velit. + client_id: Architecto itaque doloribus voluptatem consequatur quis fugiat. + client_secret: Recusandae qui culpa. + developer_token: Accusamus et atque enim. + refresh_token: Dolorum minus voluptatem. required: - credentials SetCredentialHubspotRequestBody: @@ -5222,7 +7122,7 @@ components: $ref: '#/components/schemas/HubspotCredentials' example: credentials: - private_app_token: Numquam ut nesciunt voluptas. + private_app_token: Vitae vel omnis autem accusamus mollitia. required: - credentials SetCredentialLinkedinAdsRequestBody: @@ -5232,7 +7132,7 @@ components: $ref: '#/components/schemas/LinkedinAdsCredentials' example: credentials: - access_token: Amet necessitatibus corrupti quaerat molestiae maiores. + access_token: Illo minima eaque earum. required: - credentials SetCredentialMetaAdsRequestBody: @@ -5242,8 +7142,8 @@ components: $ref: '#/components/schemas/MetaAdsCredentials' example: credentials: - access_token: Soluta quas voluptas minus possimus quos praesentium. - app_secret: Repudiandae consequatur fuga dolores. + access_token: Enim ea. + app_secret: Quidem molestiae deleniti omnis. required: - credentials SetCredentialRedditAdsRequestBody: @@ -5253,9 +7153,9 @@ components: $ref: '#/components/schemas/RedditAdsCredentials' example: credentials: - client_id: Fugit qui tempora commodi reiciendis sit alias. - client_secret: Ea enim quidem odio et sequi consequatur. - refresh_token: Provident itaque. + client_id: Magni repudiandae. + client_secret: Nihil corrupti repellat eius. + refresh_token: Qui nobis et delectus quam. required: - credentials SetCredentialTwitterAdsRequestBody: @@ -5265,10 +7165,10 @@ components: $ref: '#/components/schemas/TwitterAdsCredentials' example: credentials: - access_token: Ipsum quia ad a. - access_token_secret: Veritatis omnis excepturi. - consumer_key: Cumque libero. - consumer_secret: Id incidunt qui et ratione sit quaerat. + access_token: Deleniti placeat fuga aut saepe. + access_token_secret: Reiciendis pariatur repellat. + consumer_key: Nulla dolorum deserunt ut sit dolor. + consumer_secret: Eos nihil ipsam ex odio totam. required: - credentials TwitterAdsConnection: @@ -5277,15 +7177,15 @@ components: account_id: type: string description: Provider account identifier - example: Quisquam incidunt qui. + example: Ut quas officiis mollitia ducimus sunt repellendus. etag: type: string description: ETag header value (mirrors version) - example: Quia exercitationem consequatur velit. + example: Aliquam est autem vel eos minima. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Quia culpa iste id sit incidunt. + example: Quas est deserunt maiores consequatur eum. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5293,15 +7193,15 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Mollitia atque atque dolores accusantium non dolor. + example: Odit illo ut ut. label: type: string description: Optional friendly name - example: Nobis labore dicta. + example: Debitis atque laboriosam unde facilis. project_id: type: string description: Owning project - example: Enim qui officia id necessitatibus mollitia soluta. + example: Nam modi nostrum assumenda alias sed. status: type: string description: Connection status @@ -5314,18 +7214,18 @@ components: version: type: integer description: Optimistic-concurrency version - example: 2620236536617279883 + example: 2463177480955435124 format: int64 example: - account_id: Accusantium enim temporibus dolor ipsam. - etag: Aut ducimus dolores. - funding_instrument_id: Dolorum minus voluptatem. - has_credentials: false - id: Officiis est. - label: Tempore nemo illum ut maiores. - project_id: Qui quisquam voluptatibus sint quia. - status: error - version: 6092912427529457622 + account_id: Quisquam recusandae nesciunt et. + etag: Ut ea sequi quos qui quo et. + funding_instrument_id: Perferendis et. + has_credentials: true + id: Voluptatum pariatur non ratione quo ullam. + label: Facilis dolor consectetur sed. + project_id: Occaecati hic ut magnam facilis. + status: deleted + version: 8315950268383046552 required: - id - project_id @@ -5340,11 +7240,11 @@ components: account_id: type: string description: Provider account identifier - example: Delectus ex aut quia. + example: Illum natus doloremque facere quis et beatae. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Corporis consequatur voluptatem voluptates. + example: Magni facere saepe dignissimos eos facere. has_credentials: type: boolean description: Whether an encrypted credential is stored @@ -5352,19 +7252,19 @@ components: id: type: string description: Service-generated connection UUID (not used in paths) - example: Soluta odit odio praesentium perferendis eveniet sint. + example: Dolor sint consectetur ipsa nam ipsa magnam. label: type: string description: Optional friendly name - example: Temporibus quis iste eaque ratione debitis nam. + example: Sed est recusandae enim quidem. project_id: type: string description: Owning project - example: Quam dicta voluptate consequuntur voluptatem est. + example: Eos cum. status: type: string description: Connection status - example: deleted + example: inactive enum: - active - inactive @@ -5373,17 +7273,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 629084196159484927 + example: 1050609458141446118 format: int64 example: - account_id: Velit fuga quos. - funding_instrument_id: Sed aut quia nihil est quia. - has_credentials: true - id: Reprehenderit provident repudiandae sit. - label: Ut sunt aliquam. - project_id: Aut aut eos magnam quisquam et. - status: active - version: 2879571083312014203 + account_id: Quia sed harum. + funding_instrument_id: Ut sint laborum est quia ducimus inventore. + has_credentials: false + id: Animi dolor ex saepe ipsa ipsam. + label: Nisi consequatur non quam suscipit ea ea. + project_id: Voluptas est est accusamus. + status: inactive + version: 7559450314363992253 required: - id - project_id @@ -5397,31 +7297,31 @@ components: account_id: type: string description: Provider account identifier - example: Velit laudantium error. + example: Eaque omnis nihil itaque. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Beatae harum similique corporis id eum. + example: Sunt distinctio consequuntur enim in. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: false + example: true id: type: string description: Service-generated connection UUID (not used in paths) - example: Fugiat est itaque. + example: Delectus soluta repudiandae et. label: type: string description: Optional friendly name - example: Culpa est possimus dolores dignissimos. + example: Sequi occaecati. project_id: type: string description: Owning project - example: Fugiat aut dicta libero. + example: Exercitationem cumque libero. status: type: string description: Connection status - example: inactive + example: deleted enum: - active - inactive @@ -5430,17 +7330,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 7144064222780250488 + example: 7986908834898485319 format: int64 example: - account_id: Enim vitae officia dolorem odio. - funding_instrument_id: Excepturi minima illo dolores. + account_id: Voluptates cumque. + funding_instrument_id: Enim corrupti. has_credentials: false - id: Et itaque rem eos et deleniti dolores. - label: Facere qui omnis. - project_id: Facilis qui libero. - status: active - version: 6722562537039921847 + id: Est recusandae amet beatae. + label: Sapiente quam deserunt. + project_id: Ut amet consequatur expedita nihil molestiae soluta. + status: inactive + version: 3218820863122651463 required: - id - project_id @@ -5454,31 +7354,31 @@ components: account_id: type: string description: Provider account identifier - example: Non quo necessitatibus. + example: Rerum nesciunt veritatis consequatur porro. funding_instrument_id: type: string description: Funding instrument for the ad account - example: Laudantium nostrum earum dolores sit. + example: Ratione provident laborum. has_credentials: type: boolean description: Whether an encrypted credential is stored - example: true + example: false id: type: string description: Service-generated connection UUID (not used in paths) - example: A sint quod sunt quae et. + example: Qui voluptas culpa. label: type: string description: Optional friendly name - example: Ut mollitia nihil ducimus. + example: Delectus non aut ipsum. project_id: type: string description: Owning project - example: Iure quis. + example: Iste nihil itaque et. status: type: string description: Connection status - example: error + example: deleted enum: - active - inactive @@ -5487,17 +7387,17 @@ components: version: type: integer description: Optimistic-concurrency version - example: 3532519562043610511 + example: 1595767401527646160 format: int64 example: - account_id: At fugit dolor. - funding_instrument_id: Non exercitationem modi possimus consequatur non. + account_id: Aut sed consequatur officiis et error. + funding_instrument_id: Commodi occaecati. has_credentials: false - id: Exercitationem accusantium nihil. - label: Amet omnis. - project_id: Quod alias ipsum voluptates pariatur magni. - status: error - version: 5818865715898604715 + id: Sed voluptatem repudiandae soluta. + label: Nemo aut et qui. + project_id: Et quisquam dolores. + status: active + version: 8795877030736776782 required: - id - project_id @@ -5515,15 +7415,15 @@ components: funding_instrument_id: type: string description: Funding instrument for the ad account - example: Ut mollitia accusantium dignissimos. + example: In iure molestiae quisquam. label: type: string description: Optional friendly name - example: Fugit provident officia officiis. + example: Accusamus eos neque velit. example: account_id: 8r7gb - funding_instrument_id: Rerum ut necessitatibus illum ratione. - label: Consequatur qui molestiae velit ratione nesciunt et. + funding_instrument_id: Numquam voluptatem maiores ipsam dicta cum odit. + label: Ipsam aperiam et totam in. required: - account_id TwitterAdsCredentials: @@ -5532,30 +7432,42 @@ components: access_token: type: string description: OAuth 1.0a access token - example: At perspiciatis quisquam perferendis. + example: Iure aut tenetur ipsum ea ad ducimus. access_token_secret: type: string description: OAuth 1.0a access token secret - example: Ut alias quis nihil dolore suscipit reprehenderit. + example: Aut temporibus. consumer_key: type: string description: OAuth 1.0a consumer key - example: Id ipsa perspiciatis sunt ut velit. + example: Aut tenetur. consumer_secret: type: string description: OAuth 1.0a consumer secret - example: Aut mollitia aut molestiae exercitationem et. + example: Et et. description: X/Twitter Ads OAuth 1.0a credential set. Write-only; never returned. example: - access_token: Et aut consequatur inventore enim reiciendis. - access_token_secret: Neque ut rerum facilis quia molestiae ratione. - consumer_key: Aut animi laudantium ullam. - consumer_secret: Autem et id laboriosam nihil ea. + access_token: Necessitatibus non eum ipsum eveniet quam. + access_token_secret: Aut amet aut voluptatum magnam. + consumer_key: Laborum ipsum accusantium voluptatem libero. + consumer_secret: Eligendi non. required: - consumer_key - consumer_secret - access_token - access_token_secret + UpdateCampaignRequestBody: + type: object + properties: + campaign: + $ref: '#/components/schemas/CampaignUpdateInput' + example: + campaign: + campaign_name: Eos doloribus autem corrupti sed voluptatum laborum. + config: Perferendis qui maiores aut reiciendis. + status: Suscipit quas sunt eos nulla. + required: + - campaign UpdateGoogleAdsRequestBody: type: object properties: @@ -5575,12 +7487,12 @@ components: $ref: '#/components/schemas/HubspotConnectionConfig' example: config: - account_id: Temporibus voluptas inventore sit omnis. - brand_kit: Quis facere sint totam sapiente. - label: Sint nihil. - portal_id: Expedita veniam beatae autem. - sender_email: Placeat enim. - sender_name: Nesciunt asperiores et est. + account_id: Maxime dicta qui aut. + brand_kit: Corrupti voluptas. + label: Dolor porro inventore alias error. + portal_id: Suscipit eos nam ipsa. + sender_email: Velit et impedit facilis quaerat. + sender_name: Dolor ut velit quam omnis et dolor. required: - config UpdateLinkedinAdsRequestBody: @@ -5591,7 +7503,7 @@ components: example: config: account_id: "538170226" - label: Fugit voluptatem. + label: Temporibus et provident maiores neque. org_id: "208777" required: - config @@ -5603,9 +7515,9 @@ components: example: config: account_id: act_193556282970417 - app_id: Eligendi vero temporibus. - label: Non maiores quae sed quia. - page_id: Facilis eum ipsam. + app_id: Qui rerum est sed sed. + label: Quis quam dolor occaecati earum iusto rerum. + page_id: Cupiditate nobis. required: - config UpdateMicrosoftAdsRequestBody: @@ -5615,9 +7527,9 @@ components: $ref: '#/components/schemas/MicrosoftAdsConnectionConfig' example: config: - account_id: Sint qui praesentium ea praesentium magni. - customer_id: Iure dolore et et aut nam. - label: Consequuntur neque mollitia quia. + account_id: Ut necessitatibus dolor nemo. + customer_id: Nisi numquam nihil id unde. + label: Voluptatem sed illo numquam praesentium tenetur quas. required: - config UpdateRedditAdsRequestBody: @@ -5628,7 +7540,7 @@ components: example: config: account_id: t2_gv9wtbfa - label: Ut est nobis aspernatur. + label: Laboriosam qui id enim. required: - config UpdateTwitterAdsRequestBody: @@ -5639,8 +7551,8 @@ components: example: config: account_id: 8r7gb - funding_instrument_id: Eveniet dicta numquam doloremque. - label: Ea velit et assumenda recusandae enim. + funding_instrument_id: Dolorem non laudantium sunt minima repudiandae illo. + label: Labore esse. required: - config securitySchemes: @@ -5651,5 +7563,7 @@ components: tags: - name: lfx-v2-campaign-service-svc description: The campaign service supports Marketing Operations campaign creation and management. + - name: lfx-v2-campaign-service-briefs + description: Manage campaign briefs and their subordinate platform campaigns, including async multi-platform creation. - name: lfx-v2-campaign-service-connections description: Manage a project's singleton, per-provider ad-platform connections. diff --git a/gen/lfx_v2_campaign_service_briefs/client.go b/gen/lfx_v2_campaign_service_briefs/client.go new file mode 100644 index 00000000..13984cb3 --- /dev/null +++ b/gen/lfx_v2_campaign_service_briefs/client.go @@ -0,0 +1,207 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs client +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package lfxv2campaignservicebriefs + +import ( + "context" + + goa "goa.design/goa/v3/pkg" +) + +// Client is the "lfx-v2-campaign-service-briefs" service client. +type Client struct { + CreateBriefEndpoint goa.Endpoint + GetBriefEndpoint goa.Endpoint + UpdateBriefEndpoint goa.Endpoint + ApproveBriefEndpoint goa.Endpoint + DeleteBriefEndpoint goa.Endpoint + CreateCampaignsEndpoint goa.Endpoint + GetCampaignEndpoint goa.Endpoint + UpdateCampaignEndpoint goa.Endpoint + GetJobEndpoint goa.Endpoint +} + +// NewClient initializes a "lfx-v2-campaign-service-briefs" service client +// given the endpoints. +func NewClient(createBrief, getBrief, updateBrief, approveBrief, deleteBrief, createCampaigns, getCampaign, updateCampaign, getJob goa.Endpoint) *Client { + return &Client{ + CreateBriefEndpoint: createBrief, + GetBriefEndpoint: getBrief, + UpdateBriefEndpoint: updateBrief, + ApproveBriefEndpoint: approveBrief, + DeleteBriefEndpoint: deleteBrief, + CreateCampaignsEndpoint: createCampaigns, + GetCampaignEndpoint: getCampaign, + UpdateCampaignEndpoint: updateCampaign, + GetJobEndpoint: getJob, + } +} + +// CreateBrief calls the "create-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// CreateBrief may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) CreateBrief(ctx context.Context, p *CreateBriefPayload) (res *Brief, err error) { + var ires any + ires, err = c.CreateBriefEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Brief), nil +} + +// GetBrief calls the "get-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// GetBrief may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) GetBrief(ctx context.Context, p *GetBriefPayload) (res *Brief, err error) { + var ires any + ires, err = c.GetBriefEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Brief), nil +} + +// UpdateBrief calls the "update-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// UpdateBrief may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - "PreconditionFailed" (type *PreconditionFailedError): ETag mismatch +// - "PreconditionRequired" (type *PreconditionRequiredError): If-Match header required +// - error: internal error +func (c *Client) UpdateBrief(ctx context.Context, p *UpdateBriefPayload) (res *Brief, err error) { + var ires any + ires, err = c.UpdateBriefEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Brief), nil +} + +// ApproveBrief calls the "approve-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// ApproveBrief may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - "PreconditionFailed" (type *PreconditionFailedError): ETag mismatch +// - "PreconditionRequired" (type *PreconditionRequiredError): If-Match header required +// - error: internal error +func (c *Client) ApproveBrief(ctx context.Context, p *ApproveBriefPayload) (res *Brief, err error) { + var ires any + ires, err = c.ApproveBriefEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Brief), nil +} + +// DeleteBrief calls the "delete-brief" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// DeleteBrief may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) DeleteBrief(ctx context.Context, p *DeleteBriefPayload) (err error) { + _, err = c.DeleteBriefEndpoint(ctx, p) + return +} + +// CreateCampaigns calls the "create-campaigns" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// CreateCampaigns may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) CreateCampaigns(ctx context.Context, p *CreateCampaignsPayload) (res *JobCreateResponse, err error) { + var ires any + ires, err = c.CreateCampaignsEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*JobCreateResponse), nil +} + +// GetCampaign calls the "get-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// GetCampaign may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) GetCampaign(ctx context.Context, p *GetCampaignPayload) (res *Campaign, err error) { + var ires any + ires, err = c.GetCampaignEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Campaign), nil +} + +// UpdateCampaign calls the "update-campaign" endpoint of the +// "lfx-v2-campaign-service-briefs" service. +// UpdateCampaign may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - "PreconditionFailed" (type *PreconditionFailedError): ETag mismatch +// - "PreconditionRequired" (type *PreconditionRequiredError): If-Match header required +// - error: internal error +func (c *Client) UpdateCampaign(ctx context.Context, p *UpdateCampaignPayload) (res *Campaign, err error) { + var ires any + ires, err = c.UpdateCampaignEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*Campaign), nil +} + +// GetJob calls the "get-job" endpoint of the "lfx-v2-campaign-service-briefs" +// service. +// GetJob may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ConnServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) GetJob(ctx context.Context, p *GetJobPayload) (res *JobPollResponse, err error) { + var ires any + ires, err = c.GetJobEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*JobPollResponse), nil +} diff --git a/gen/lfx_v2_campaign_service_briefs/endpoints.go b/gen/lfx_v2_campaign_service_briefs/endpoints.go new file mode 100644 index 00000000..9a30542c --- /dev/null +++ b/gen/lfx_v2_campaign_service_briefs/endpoints.go @@ -0,0 +1,267 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs endpoints +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package lfxv2campaignservicebriefs + +import ( + "context" + + goa "goa.design/goa/v3/pkg" + "goa.design/goa/v3/security" +) + +// Endpoints wraps the "lfx-v2-campaign-service-briefs" service endpoints. +type Endpoints struct { + CreateBrief goa.Endpoint + GetBrief goa.Endpoint + UpdateBrief goa.Endpoint + ApproveBrief goa.Endpoint + DeleteBrief goa.Endpoint + CreateCampaigns goa.Endpoint + GetCampaign goa.Endpoint + UpdateCampaign goa.Endpoint + GetJob goa.Endpoint +} + +// NewEndpoints wraps the methods of the "lfx-v2-campaign-service-briefs" +// service with endpoints. +func NewEndpoints(s Service) *Endpoints { + // Casting service to Auther interface + a := s.(Auther) + return &Endpoints{ + CreateBrief: NewCreateBriefEndpoint(s, a.JWTAuth), + GetBrief: NewGetBriefEndpoint(s, a.JWTAuth), + UpdateBrief: NewUpdateBriefEndpoint(s, a.JWTAuth), + ApproveBrief: NewApproveBriefEndpoint(s, a.JWTAuth), + DeleteBrief: NewDeleteBriefEndpoint(s, a.JWTAuth), + CreateCampaigns: NewCreateCampaignsEndpoint(s, a.JWTAuth), + GetCampaign: NewGetCampaignEndpoint(s, a.JWTAuth), + UpdateCampaign: NewUpdateCampaignEndpoint(s, a.JWTAuth), + GetJob: NewGetJobEndpoint(s, a.JWTAuth), + } +} + +// Use applies the given middleware to all the "lfx-v2-campaign-service-briefs" +// service endpoints. +func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { + e.CreateBrief = m(e.CreateBrief) + e.GetBrief = m(e.GetBrief) + e.UpdateBrief = m(e.UpdateBrief) + e.ApproveBrief = m(e.ApproveBrief) + e.DeleteBrief = m(e.DeleteBrief) + e.CreateCampaigns = m(e.CreateCampaigns) + e.GetCampaign = m(e.GetCampaign) + e.UpdateCampaign = m(e.UpdateCampaign) + e.GetJob = m(e.GetJob) +} + +// NewCreateBriefEndpoint returns an endpoint function that calls the method +// "create-brief" of service "lfx-v2-campaign-service-briefs". +func NewCreateBriefEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*CreateBriefPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.CreateBrief(ctx, p) + } +} + +// NewGetBriefEndpoint returns an endpoint function that calls the method +// "get-brief" of service "lfx-v2-campaign-service-briefs". +func NewGetBriefEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*GetBriefPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.GetBrief(ctx, p) + } +} + +// NewUpdateBriefEndpoint returns an endpoint function that calls the method +// "update-brief" of service "lfx-v2-campaign-service-briefs". +func NewUpdateBriefEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*UpdateBriefPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.UpdateBrief(ctx, p) + } +} + +// NewApproveBriefEndpoint returns an endpoint function that calls the method +// "approve-brief" of service "lfx-v2-campaign-service-briefs". +func NewApproveBriefEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*ApproveBriefPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.ApproveBrief(ctx, p) + } +} + +// NewDeleteBriefEndpoint returns an endpoint function that calls the method +// "delete-brief" of service "lfx-v2-campaign-service-briefs". +func NewDeleteBriefEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*DeleteBriefPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return nil, s.DeleteBrief(ctx, p) + } +} + +// NewCreateCampaignsEndpoint returns an endpoint function that calls the +// method "create-campaigns" of service "lfx-v2-campaign-service-briefs". +func NewCreateCampaignsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*CreateCampaignsPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.CreateCampaigns(ctx, p) + } +} + +// NewGetCampaignEndpoint returns an endpoint function that calls the method +// "get-campaign" of service "lfx-v2-campaign-service-briefs". +func NewGetCampaignEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*GetCampaignPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.GetCampaign(ctx, p) + } +} + +// NewUpdateCampaignEndpoint returns an endpoint function that calls the method +// "update-campaign" of service "lfx-v2-campaign-service-briefs". +func NewUpdateCampaignEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*UpdateCampaignPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.UpdateCampaign(ctx, p) + } +} + +// NewGetJobEndpoint returns an endpoint function that calls the method +// "get-job" of service "lfx-v2-campaign-service-briefs". +func NewGetJobEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*GetJobPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.GetJob(ctx, p) + } +} diff --git a/gen/lfx_v2_campaign_service_briefs/service.go b/gen/lfx_v2_campaign_service_briefs/service.go new file mode 100644 index 00000000..e00c45fe --- /dev/null +++ b/gen/lfx_v2_campaign_service_briefs/service.go @@ -0,0 +1,466 @@ +// Code generated by goa v3.25.3, DO NOT EDIT. +// +// lfx-v2-campaign-service-briefs service +// +// Command: +// $ goa gen github.com/linuxfoundation/lfx-v2-campaign-service/design + +package lfxv2campaignservicebriefs + +import ( + "context" + + "goa.design/goa/v3/security" +) + +// Manage campaign briefs and their subordinate platform campaigns, including +// async multi-platform creation. +type Service interface { + // Create a brief. + CreateBrief(context.Context, *CreateBriefPayload) (res *Brief, err error) + // Get a brief; returns ETag. + GetBrief(context.Context, *GetBriefPayload) (res *Brief, err error) + // Replace a brief (requires If-Match). + UpdateBrief(context.Context, *UpdateBriefPayload) (res *Brief, err error) + // Approve a brief for campaign creation. + ApproveBrief(context.Context, *ApproveBriefPayload) (res *Brief, err error) + // Archive a brief (soft delete). + DeleteBrief(context.Context, *DeleteBriefPayload) (err error) + // Create campaigns across the selected platforms (async -> job). + CreateCampaigns(context.Context, *CreateCampaignsPayload) (res *JobCreateResponse, err error) + // Get one campaign under a brief; returns ETag. + GetCampaign(context.Context, *GetCampaignPayload) (res *Campaign, err error) + // Replace a campaign (requires If-Match). + UpdateCampaign(context.Context, *UpdateCampaignPayload) (res *Campaign, err error) + // Poll campaign-creation job status. + GetJob(context.Context, *GetJobPayload) (res *JobPollResponse, err error) +} + +// Auther defines the authorization functions to be implemented by the service. +type Auther interface { + // JWTAuth implements the authorization logic for the JWT security scheme. + JWTAuth(ctx context.Context, token string, schema *security.JWTScheme) (context.Context, error) +} + +// APIName is the name of the API as defined in the design. +const APIName = "lfx-v2-campaign-service" + +// APIVersion is the version of the API as defined in the design. +const APIVersion = "0.0.1" + +// ServiceName is the name of the service as defined in the design. This is the +// same value that is set in the endpoint request contexts under the ServiceKey +// key. +const ServiceName = "lfx-v2-campaign-service-briefs" + +// MethodNames lists the service method names as defined in the design. These +// are the same values that are set in the endpoint request contexts under the +// MethodKey key. +var MethodNames = [9]string{"create-brief", "get-brief", "update-brief", "approve-brief", "delete-brief", "create-campaigns", "get-campaign", "update-campaign", "get-job"} + +// ApproveBriefPayload is the payload type of the +// lfx-v2-campaign-service-briefs service approve-brief method. +type ApproveBriefPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string + // If-Match header carrying the current ETag/version + IfMatch *string +} + +// Brief is the result type of the lfx-v2-campaign-service-briefs service +// create-brief method. +type Brief struct { + // Brief UUID + ID string + // Owning project + ProjectID string + // Funnel context + ProgramType string + // Event/course slug (unique within the project) + EventSlug string + // Event/course page URL + URL *string + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string + // Extracted event/course details + EventDetails any + // Ad copy + Copy any + // Keyword list + Keywords any + // Targeting recommendation + Targeting any + // Lifecycle status + Status string + // Optimistic-concurrency version + Version int64 + // ETag header value (mirrors version) + Etag *string +} + +type BriefInput struct { + // Funnel context + ProgramType string + // Event/course slug (unique within the project) + EventSlug string + // Event/course page URL + URL *string + // Suggested default platforms (a planning hint; binding selection is on the + // campaign) + Platforms []string + // Extracted event/course details + EventDetails any + // Ad copy + Copy any + // Keyword list + Keywords any + // Targeting recommendation + Targeting any +} + +// Campaign is the result type of the lfx-v2-campaign-service-briefs service +// get-campaign method. +type Campaign struct { + // Campaign UUID + ID string + // Owning project + ProjectID string + // Parent brief + BriefID string + // Channel + Platform string + // ID returned by the ad platform + PlatformCampaignID *string + // Campaign name + CampaignName string + // Campaign status + Status string + // Optimistic-concurrency version + Version int64 + // ETag header value (mirrors version) + Etag *string +} + +type CampaignCreateInput struct { + // Platforms to create campaigns on (binding selection) + Platforms []string + // Per-platform campaign configuration + Config any +} + +type CampaignUpdateInput struct { + // Campaign name + CampaignName string + // Campaign status + Status string + // Campaign configuration snapshot + Config any +} + +// CreateBriefPayload is the payload type of the lfx-v2-campaign-service-briefs +// service create-brief method. +type CreateBriefPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + Brief *BriefInput +} + +// CreateCampaignsPayload is the payload type of the +// lfx-v2-campaign-service-briefs service create-campaigns method. +type CreateCampaignsPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string + Input *CampaignCreateInput +} + +// DeleteBriefPayload is the payload type of the lfx-v2-campaign-service-briefs +// service delete-brief method. +type DeleteBriefPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string +} + +// GetBriefPayload is the payload type of the lfx-v2-campaign-service-briefs +// service get-brief method. +type GetBriefPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string +} + +// GetCampaignPayload is the payload type of the lfx-v2-campaign-service-briefs +// service get-campaign method. +type GetCampaignPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string + // Campaign UUID + CampaignID string +} + +// GetJobPayload is the payload type of the lfx-v2-campaign-service-briefs +// service get-job method. +type GetJobPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Job UUID + JobID string +} + +// JobCreateResponse is the result type of the lfx-v2-campaign-service-briefs +// service create-campaigns method. +type JobCreateResponse struct { + // Poll GET /projects/{projectId}/jobs/{jobId} + JobID string + // Initial status (always 'queued' on create) + Status string + // Platforms this job will create on + Platforms []string +} + +// JobPollResponse is the result type of the lfx-v2-campaign-service-briefs +// service get-job method. +type JobPollResponse struct { + // Job UUID + JobID string + // Job status + Status string + // Per-platform results, written once when the job reaches a terminal state + Result []*PlatformResult + // Terminal error, if any + Error *string +} + +type PlatformResult struct { + // Platform this result is for + Platform string + // Whether the campaign was created (or reused) successfully + OK bool + // Upstream platform campaign id. Present when ok; also set on the specific + // failure where the upstream campaign was created but recording it failed, so + // the orphaned id isn't lost. + CampaignID *string + // Failure reason (present when not ok) + Error *string +} + +// UpdateBriefPayload is the payload type of the lfx-v2-campaign-service-briefs +// service update-brief method. +type UpdateBriefPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string + // If-Match header carrying the current ETag/version + IfMatch *string + Brief *BriefInput +} + +// UpdateCampaignPayload is the payload type of the +// lfx-v2-campaign-service-briefs service update-campaign method. +type UpdateCampaignPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Project UUID or slug that scopes the connection + ProjectID string + // Brief UUID + BriefID string + // Campaign UUID + CampaignID string + // If-Match header carrying the current ETag/version + IfMatch *string + Campaign *CampaignUpdateInput +} + +type BadRequestError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type ConflictError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type ConnServiceUnavailableError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type InternalServerError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type NotFoundError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type PreconditionFailedError struct { + // HTTP status code + Code string + // Error message + Message string +} + +type PreconditionRequiredError struct { + // HTTP status code + Code string + // Error message + Message string +} + +// Error returns an error description. +func (e *BadRequestError) Error() string { + return "" +} + +// ErrorName returns "bad-request-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *BadRequestError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "bad-request-error". +func (e *BadRequestError) GoaErrorName() string { + return "BadRequest" +} + +// Error returns an error description. +func (e *ConflictError) Error() string { + return "" +} + +// ErrorName returns "conflict-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *ConflictError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "conflict-error". +func (e *ConflictError) GoaErrorName() string { + return "Conflict" +} + +// Error returns an error description. +func (e *ConnServiceUnavailableError) Error() string { + return "" +} + +// ErrorName returns "conn-service-unavailable-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *ConnServiceUnavailableError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "conn-service-unavailable-error". +func (e *ConnServiceUnavailableError) GoaErrorName() string { + return "ServiceUnavailable" +} + +// Error returns an error description. +func (e *InternalServerError) Error() string { + return "" +} + +// ErrorName returns "internal-server-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *InternalServerError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "internal-server-error". +func (e *InternalServerError) GoaErrorName() string { + return "InternalServerError" +} + +// Error returns an error description. +func (e *NotFoundError) Error() string { + return "" +} + +// ErrorName returns "not-found-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *NotFoundError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "not-found-error". +func (e *NotFoundError) GoaErrorName() string { + return "NotFound" +} + +// Error returns an error description. +func (e *PreconditionFailedError) Error() string { + return "" +} + +// ErrorName returns "precondition-failed-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *PreconditionFailedError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "precondition-failed-error". +func (e *PreconditionFailedError) GoaErrorName() string { + return "PreconditionFailed" +} + +// Error returns an error description. +func (e *PreconditionRequiredError) Error() string { + return "" +} + +// ErrorName returns "precondition-required-error". +// +// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 +func (e *PreconditionRequiredError) ErrorName() string { + return e.GoaErrorName() +} + +// GoaErrorName returns "precondition-required-error". +func (e *PreconditionRequiredError) GoaErrorName() string { + return "PreconditionRequired" +} diff --git a/go.mod b/go.mod index 8a07f1ec..57bcb73b 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 goa.design/clue v1.2.1 goa.design/goa/v3 v3.25.3 + golang.org/x/sync v0.21.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -56,7 +57,6 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.56.0 // indirect - golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/term v0.44.0 // indirect golang.org/x/text v0.38.0 // indirect diff --git a/internal/container/container.go b/internal/container/container.go index 468c726f..59320df0 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -10,12 +10,15 @@ import ( "log/slog" "time" + briefsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" connsvc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_connections" svc "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_svc" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/infrastructure/config" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/infrastructure/crypto" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/infrastructure/postgres" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/service" + "github.com/linuxfoundation/lfx-v2-campaign-service/pkg/constants" ) // startupDBTimeout bounds the database connection/ping during container init so @@ -23,13 +26,63 @@ import ( // restart it) rather than wedging startup indefinitely. const startupDBTimeout = 15 * time.Second +// dispatchDrainTimeout bounds how long Container.Close waits for in-flight +// campaign dispatch to finish before the pool is closed. Together with the +// orchestrator's post-cancel grace (service.CancelGracePeriod) it forms +// ContainerCloseTimeout, which is reserved out of the overall graceful-shutdown +// budget (constants.DefaultShutdownTimeout) so the HTTP-drain phase and this +// phase can't sum past it and get SIGKILLed. Validated by the init() below. +// +// Sized so dispatchDrainTimeout + CancelGracePeriod leaves a positive HTTP-drain +// budget: CancelGracePeriod grew to cover the post-provider persist AND the +// terminal finalize write (both detached, both must complete during grace), so +// the drain window is trimmed to keep the total within DefaultShutdownTimeout. +const dispatchDrainTimeout = 6 * time.Second + +// ContainerCloseTimeout is the wall-clock budget for Container.Close: the +// orchestrator drain (dispatchDrainTimeout) plus its post-cancel grace +// (service.CancelGracePeriod). The server budgets the HTTP-shutdown phase and +// this container-close phase separately (see HTTPShutdownTimeout), so the total +// graceful shutdown is a true sum bounded by constants.DefaultShutdownTimeout. +const ContainerCloseTimeout = dispatchDrainTimeout + service.CancelGracePeriod + +// HTTPShutdownTimeout is the wall-clock budget for draining in-flight HTTP +// handlers before the container is closed. It is whatever remains of the overall +// graceful-shutdown budget after the container-close phase is reserved, so the +// two sequential phases can never sum past DefaultShutdownTimeout (which would +// otherwise risk a SIGKILL mid-drain — the orchestrator's grace timer is +// wall-clock, not bound by a shared context). +const HTTPShutdownTimeout = constants.DefaultShutdownTimeout - ContainerCloseTimeout + +// HandlerDrainTimeout is a SEPARATE, dedicated budget for waiting on in-flight +// handler goroutines to return AFTER a forced srv.Close(). It must not be derived +// from the HTTP-shutdown context's remaining time: when srv.Shutdown times out, +// that context is already exhausted, so a "remaining budget" would be zero and +// the wait would return immediately — defeating the tracker exactly when a +// straggler is running. Reserving a small fixed slice guarantees a bounded wait +// in that case. It is carved from HTTPShutdownTimeout so the total still fits. +const HandlerDrainTimeout = 2 * time.Second + +func init() { + if dispatchDrainTimeout+service.CancelGracePeriod > constants.DefaultShutdownTimeout { + panic("dispatchDrainTimeout + service.CancelGracePeriod exceeds DefaultShutdownTimeout") + } + // The HTTP phase must have a positive budget once the container-close phase + // is reserved; otherwise HTTP handlers would get no drain window at all. + if HTTPShutdownTimeout <= 0 { + panic("HTTPShutdownTimeout is non-positive: ContainerCloseTimeout consumes the entire DefaultShutdownTimeout") + } +} + // Container holds all application dependencies. type Container struct { Config *config.Config Service svc.Service Connections connsvc.Service + Briefs briefsvc.Service pool *postgres.Pool + orch *service.Orchestrator } // NewContainer creates and wires all application dependencies. @@ -49,12 +102,13 @@ func NewContainer(cfg *config.Config) (*Container, error) { c := &Container{Config: cfg} if cfg.DatabaseURL == "" { - slog.Warn("database URL not set; connection endpoints will return 503 Service Unavailable") + slog.Warn("database URL not set; connection and brief/campaign endpoints will return 503 Service Unavailable") c.Service = service.NewCampaignService(nil) - // Wire the connection service with a nil repo so its routes are still - // mounted and return the typed 503 ServiceUnavailable advertised by the - // OpenAPI contract, rather than a bare 404 from unmounted routes. + // Wire the connection + brief services with nil repos so their routes are + // still mounted and return the typed 503 ServiceUnavailable advertised by + // the OpenAPI contract, rather than a bare 404 from unmounted routes. c.Connections = service.NewConnectionService(nil, nil) + c.Briefs = service.NewBriefService(nil, nil, nil, nil) slog.Info("dependency container initialized (no database)") return c, nil } @@ -89,6 +143,38 @@ func NewContainer(cfg *config.Config) (*Container, error) { repo := postgres.NewConnectionRepo(pool) c.Connections = service.NewConnectionService(repo, enc) + briefRepo := postgres.NewBriefRepo(pool) + campaignRepo := postgres.NewCampaignRepo(pool) + jobRepo := postgres.NewJobRepo(pool) + // No platform dispatchers are registered yet; campaign creation records a + // job whose platforms report "no dispatcher" until the per-platform adapters + // land (LFXV2-2636..2640). The orchestration flow (job lifecycle, + // persistence) is exercised end to end regardless. Log a startup warning so + // this gap is visible in production logs rather than silently producing jobs + // that always finish "failed" with "no dispatcher registered". + dispatchers := map[model.Provider]service.PlatformDispatcher{} + if len(dispatchers) == 0 { + slog.Warn("no platform dispatchers registered; campaign creation will record jobs but perform no upstream dispatch") + } + orch := service.NewOrchestrator(campaignRepo, jobRepo, dispatchers) + c.orch = orch + c.Briefs = service.NewBriefService(briefRepo, campaignRepo, jobRepo, orch) + + // Recover jobs orphaned by a previous pod's restart: a queued/running job's + // dispatch goroutine lived only in that process, so fail them forward now + // rather than leaving them non-terminal forever. Bounded by the startup + // deadline; a failure here is logged but not fatal (the service can still run). + if n, rerr := jobRepo.FailStuckJobs(startupCtx, "job did not complete before a service restart"); rerr != nil { + slog.Warn("failed to recover stuck jobs on startup", "error", rerr) + } else if n > 0 { + slog.Info("recovered stuck jobs on startup", "count", n) + } + + // The startup scan can't recover a job orphaned by a crash younger than the + // stale cutoff (too new to look stuck at boot, never re-examined). A periodic + // sweep catches those; it stops on Shutdown via the orchestrator's root ctx. + orch.StartRecoverySweeper() + // The health service's readiness depends on the database pool (Readyz). c.Service = service.NewCampaignService(pool) @@ -100,10 +186,32 @@ func NewContainer(cfg *config.Config) (*Container, error) { return c, nil } -// Close releases any resources held by the container. -func (c *Container) Close() error { +// Close releases any resources held by the container. It first drains in-flight +// campaign dispatch so a dispatch that already created an upstream campaign +// isn't cut off before it persists, THEN closes the database pool. +// +// Orchestrator.Shutdown runs two separately-budgeted phases: a clean drain +// bounded by dispatchDrainTimeout, then (only if that elapses) a post-cancel +// grace bounded by CancelGracePeriod. Both must fit within ctx, so ctx MUST +// carry the full ContainerCloseTimeout (= dispatchDrainTimeout + +// CancelGracePeriod), not just the drain timeout — otherwise the grace phase +// would have zero budget and the pool could close while a just-cancelled +// dispatch is still finalizing job/campaign state. +func (c *Container) Close(ctx context.Context) error { + // Capture the orchestrator shutdown error but do NOT early-return on it: the + // pool must still be closed even if the drain timed out with dispatches still + // running. Returning the error (rather than swallowing it) makes a shutdown + // failure — dispatches still running when the pool was closed — observable to + // the caller's "container close error" branch and its logs. + var shutdownErr error + if c.orch != nil { + if err := c.orch.Shutdown(ctx, dispatchDrainTimeout); err != nil { + slog.Warn("timed out draining in-flight dispatch on shutdown", "error", err) + shutdownErr = fmt.Errorf("drain in-flight dispatch: %w", err) + } + } if c.pool != nil { c.pool.Close() } - return nil + return shutdownErr } diff --git a/internal/container/container_test.go b/internal/container/container_test.go index 8f1ee41d..e18ee759 100644 --- a/internal/container/container_test.go +++ b/internal/container/container_test.go @@ -4,13 +4,130 @@ package container import ( + "context" + "encoding/json" + "sync" "testing" + "time" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" "github.com/linuxfoundation/lfx-v2-campaign-service/internal/infrastructure/config" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/service" + "github.com/linuxfoundation/lfx-v2-campaign-service/pkg/constants" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// TestShutdownBudgetComposes verifies the two graceful-shutdown phases sum to at +// most the overall budget, so the sequential srv.Shutdown then Container.Close +// can never overrun DefaultShutdownTimeout (which would risk a SIGKILL +// mid-drain). This guards the invariant the init() in container.go panics on. +func TestShutdownBudgetComposes(t *testing.T) { + // The container-close phase reserves drain + post-cancel grace. + assert.Equal(t, dispatchDrainTimeout+service.CancelGracePeriod, ContainerCloseTimeout) + // The HTTP phase gets a positive share of the remaining budget. + assert.Positive(t, HTTPShutdownTimeout, "HTTP shutdown phase must have a positive budget") + // The two phases together stay within the overall budget. + assert.LessOrEqual(t, HTTPShutdownTimeout+ContainerCloseTimeout, constants.DefaultShutdownTimeout) +} + +// blockingDispatcher blocks until its context is cancelled, so Orchestrator.Shutdown +// hits the drain deadline and returns an error — letting the test prove Close +// propagates (rather than swallows) that error. +type blockingDispatcher struct{ started chan struct{} } + +func (d *blockingDispatcher) Dispatch(ctx context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + select { + case d.started <- struct{}{}: + default: + } + <-ctx.Done() + return &model.Campaign{PlatformCampaignID: "pc-" + string(p)}, nil +} + +// stubJobRepo is a minimal in-memory JobRepository for the Close test. +type stubJobRepo struct { + mu sync.Mutex + seq int + jobs map[string]*model.CampaignJob +} + +func newStubJobRepo() *stubJobRepo { return &stubJobRepo{jobs: map[string]*model.CampaignJob{}} } + +func (r *stubJobRepo) create() (*model.CampaignJob, error) { + r.mu.Lock() + defer r.mu.Unlock() + r.seq++ + j := &model.CampaignJob{ID: "job-" + string(rune('a'+r.seq)), Status: model.JobQueued} + r.jobs[j.ID] = j + return j, nil +} +func (r *stubJobRepo) CreateJob(context.Context, string) (*model.CampaignJob, error) { + return r.create() +} +func (r *stubJobRepo) CreateJobForApprovedBrief(context.Context, string, int64) (*model.CampaignJob, error) { + return r.create() +} +func (r *stubJobRepo) GetJob(context.Context, string, string) (*model.CampaignJob, error) { + return nil, domain.ErrNotFound +} +func (r *stubJobRepo) UpdateJobStatus(context.Context, string, model.JobStatus, []byte, string) error { + return nil +} +func (r *stubJobRepo) FailStuckJobs(context.Context, string) (int64, error) { return 0, nil } + +// stubCampaignRepo is a minimal in-memory CampaignRepository for the Close test. +type stubCampaignRepo struct{} + +func (stubCampaignRepo) GetCampaign(context.Context, string, string, string) (*model.Campaign, error) { + return nil, domain.ErrNotFound +} +func (stubCampaignRepo) GetCampaignByPlatform(context.Context, string, string, model.Provider) (*model.Campaign, error) { + return nil, domain.ErrNotFound +} +func (stubCampaignRepo) ClaimCampaignDispatch(context.Context, string, string, model.Provider, string) (bool, *model.Campaign, error) { + return true, &model.Campaign{Status: "pending"}, nil +} +func (stubCampaignRepo) DeleteDispatchClaim(context.Context, string, model.Provider) error { + return nil +} +func (stubCampaignRepo) UpsertCampaign(_ context.Context, c *model.Campaign) (*model.Campaign, error) { + return c, nil +} +func (stubCampaignRepo) ReplaceCampaign(context.Context, *model.Campaign, int64) (*model.Campaign, error) { + return nil, domain.ErrNotFound +} + +// TestClose_PropagatesShutdownError verifies Container.Close returns (does not +// swallow) the orchestrator shutdown error when a dispatch is still running at +// drain time, so the caller's "container close error" branch can observe that +// dispatches were still running when the pool was closed. The pool must still be +// closed regardless (here the pool is nil, exercising the error-propagation path +// without a real database). +func TestClose_PropagatesShutdownError(t *testing.T) { + disp := &blockingDispatcher{started: make(chan struct{}, 1)} + orch := service.NewOrchestrator(stubCampaignRepo{}, newStubJobRepo(), map[model.Provider]service.PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + if _, err := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil); err != nil { + t.Fatalf("Start: %v", err) + } + <-disp.started // dispatch is in-flight and will block until its ctx is cancelled + + c := &Container{orch: orch} // nil pool: exercise the error path without a DB + + // A short outer budget forces the drain to time out and the grace to expire + // with the dispatch still blocked, so Shutdown returns a non-nil error. + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + err := c.Close(ctx) + if err == nil { + t.Fatal("Close returned nil; a shutdown timeout (dispatches still running) must be observable to the caller") + } +} + func TestNewContainer_NoDatabase(t *testing.T) { cfg := &config.Config{ Host: "*", @@ -22,7 +139,7 @@ func TestNewContainer_NoDatabase(t *testing.T) { require.NotNil(t, cont) assert.NotNil(t, cont.Service) assert.NotNil(t, cont.Connections) - require.NoError(t, cont.Close()) + require.NoError(t, cont.Close(context.Background())) } func TestNewContainer_UnsupportedEngine(t *testing.T) { diff --git a/internal/domain/brief_port.go b/internal/domain/brief_port.go index f8d68986..ef28500e 100644 --- a/internal/domain/brief_port.go +++ b/internal/domain/brief_port.go @@ -22,8 +22,10 @@ type BriefWriter interface { CreateBrief(ctx context.Context, b *model.CampaignBrief) (*model.CampaignBrief, error) // ReplaceBrief replaces a brief's mutable fields, gating on expectedVersion. ReplaceBrief(ctx context.Context, b *model.CampaignBrief, expectedVersion int64) (*model.CampaignBrief, error) - // Approve marks a brief approved, recording the actor. - Approve(ctx context.Context, projectID, id string, by *model.Actor) (*model.CampaignBrief, error) + // Approve marks a brief approved, recording the actor. It is gated on + // expectedVersion (optimistic concurrency): approving a stale version returns + // ErrPreconditionFailed so a concurrent replace can't be approved by accident. + Approve(ctx context.Context, projectID, id string, by *model.Actor, expectedVersion int64) (*model.CampaignBrief, error) // ArchiveBrief soft-archives a brief (status = archived). ArchiveBrief(ctx context.Context, projectID, id string) error } @@ -38,6 +40,27 @@ type BriefRepository interface { type CampaignReader interface { // GetCampaign returns a single campaign under a brief, or ErrNotFound. GetCampaign(ctx context.Context, projectID, briefID, id string) (*model.Campaign, error) + // GetCampaignByPlatform returns the campaign for a (brief, platform) pair, or + // ErrNotFound. Used to make dispatch idempotent: a brief already dispatched to + // a platform must not create a second upstream (paid) campaign on retry. Scoped + // by projectID for tenant isolation, matching GetCampaign/ClaimCampaignDispatch. + GetCampaignByPlatform(ctx context.Context, projectID, briefID string, platform model.Provider) (*model.Campaign, error) + // ClaimCampaignDispatch atomically claims the right to dispatch (brief, + // platform) by inserting a placeholder campaign row (status 'pending') via + // INSERT ... ON CONFLICT (brief_id, platform) DO NOTHING. Exactly one worker + // wins across all replicas — the (brief_id, platform) unique index arbitrates, + // with no held connection and no blocking lock. It returns: + // - claimed=true, row=the pending row → this worker owns the dispatch; + // - claimed=false, row=the existing row → another worker already claimed or + // completed it; the caller reuses that row instead of dispatching again. + // The placeholder row also survives an upstream-create-then-crash, making the + // orphan recoverable (its status stays 'pending'). + ClaimCampaignDispatch(ctx context.Context, projectID, briefID string, platform model.Provider, jobID string) (claimed bool, row *model.Campaign, err error) + // DeleteDispatchClaim removes a still-'pending' claim row for (brief, platform) + // so the pair can be retried after a dispatch fails before the upstream + // campaign is created. It only deletes rows still in 'pending' status, so it + // can never remove a real (created) campaign. + DeleteDispatchClaim(ctx context.Context, briefID string, platform model.Provider) error } // CampaignWriter mutates campaigns. @@ -59,9 +82,27 @@ type CampaignRepository interface { type JobRepository interface { // CreateJob inserts a queued job for a brief. CreateJob(ctx context.Context, briefID string) (*model.CampaignJob, error) + // CreateJobForApprovedBrief inserts a queued job ONLY if the brief is still + // approved at expectedVersion, re-verifying the approval atomically with the job + // insert inside one transaction. This closes the approve→dispatch TOCTOU race: + // a concurrent ReplaceBrief (which resets the brief to draft and bumps version) + // or ArchiveBrief committing between the caller's approval read and job creation + // bumps the brief's version and must prevent the job from being created against + // that stale approval, returning ErrStaleApproval instead of launching paid + // campaigns from a stale "approved" snapshot. The guard takes a SELECT ... FOR + // UPDATE row lock on the brief before the insert so it serializes against any + // concurrent replace/archive of that row — a lone guarded INSERT ... WHERE + // EXISTS would NOT, because under READ COMMITTED its snapshot can miss a mutation + // that commits just before the statement runs (see the implementation for the + // full isolation reasoning). + CreateJobForApprovedBrief(ctx context.Context, briefID string, expectedVersion int64) (*model.CampaignJob, error) // GetJob returns a job by id, or ErrNotFound. - GetJob(ctx context.Context, id string) (*model.CampaignJob, error) + GetJob(ctx context.Context, projectID, id string) (*model.CampaignJob, error) // UpdateJobStatus sets a job's status (any JobStatus, e.g. running or a // terminal succeeded/partial/failed) and its result/error. UpdateJobStatus(ctx context.Context, id string, status model.JobStatus, result []byte, jobErr string) error + // FailStuckJobs marks every non-terminal (queued/running) job as failed with + // the given error, returning the count. Called on startup to recover jobs + // orphaned by a pod restart (their in-memory dispatch goroutine is gone). + FailStuckJobs(ctx context.Context, jobErr string) (int64, error) } diff --git a/internal/domain/errors.go b/internal/domain/errors.go index 4abab339..39f97e63 100644 --- a/internal/domain/errors.go +++ b/internal/domain/errors.go @@ -19,6 +19,14 @@ var ( // 409. ErrConflict = errors.New("resource already exists") + // ErrStaleApproval indicates the approve→dispatch guard fired: the brief was + // no longer approved at the expected version when the job was created (a + // concurrent replace/archive committed in the window, or it lost approval). + // It is distinct from ErrConflict (a uniqueness violation) because the client + // remedy differs — refresh and re-approve, then retry — even though both map + // to 409. Maps to 409. + ErrStaleApproval = errors.New("brief is no longer approved at the expected version") + // ErrPreconditionFailed indicates an optimistic-concurrency version // mismatch on a conditional update (stale If-Match). Maps to 412. ErrPreconditionFailed = errors.New("version precondition failed") diff --git a/internal/infrastructure/postgres/brief_repo.go b/internal/infrastructure/postgres/brief_repo.go index f1c55059..54f1c598 100644 --- a/internal/infrastructure/postgres/brief_repo.go +++ b/internal/infrastructure/postgres/brief_repo.go @@ -67,42 +67,74 @@ func (r *BriefRepo) CreateBrief(ctx context.Context, b *model.CampaignBrief) (*m // ReplaceBrief replaces mutable fields, gating on expectedVersion. func (r *BriefRepo) ReplaceBrief(ctx context.Context, b *model.CampaignBrief, expectedVersion int64) (*model.CampaignBrief, error) { + // Replacing brief content invalidates any prior approval: reset the brief to + // 'draft' and clear the approver so a modified brief cannot silently retain + // status='approved' (which would let changed ad inputs be treated as approved + // and dispatched without re-review). event_slug is included so a slug change + // is actually persisted (it is subject to the partial-unique index, which + // surfaces a conflict if the new slug collides with a live brief). q := `UPDATE campaign_briefs SET - program_type=$1, url=$2, platforms=$3, event_details=$4, copy=$5, keywords=$6, targeting=$7, + program_type=$1, event_slug=$2, url=$3, platforms=$4, event_details=$5, copy=$6, keywords=$7, targeting=$8, + status='draft', approved_by=NULL, approved_at=NULL, version=version+1, updated_at=now() - WHERE id=$8 AND project_id=$9 AND version=$10 AND status <> 'archived'` + WHERE id=$9 AND project_id=$10 AND version=$11 AND status <> 'archived'` tag, err := r.db.Exec(ctx, q, - string(b.ProgramType), nullStr(b.URL), nullJSON(b.Platforms), nullJSON(b.EventDetails), + string(b.ProgramType), b.EventSlug, nullStr(b.URL), nullJSON(b.Platforms), nullJSON(b.EventDetails), nullJSON(b.Copy), nullJSON(b.Keywords), nullJSON(b.Targeting), b.ID, b.ProjectID, expectedVersion, ) if err != nil { + // A slug change that collides with another live brief trips the partial + // unique index; surface it as a conflict (409) rather than a 500. + if isUniqueViolation(err) { + return nil, domain.ErrConflict + } return nil, fmt.Errorf("replace brief: %w", err) } if tag.RowsAffected() == 0 { - if _, gerr := r.GetBrief(ctx, b.ProjectID, b.ID); errors.Is(gerr, domain.ErrNotFound) { + // Distinguish missing from stale version. Surface a transient re-fetch + // error rather than masking it as a precondition failure (which would make + // the caller retry with a fresh ETag instead of backing off on a server + // error), consistent with ConnectionRepo.Update. + _, gerr := r.GetBrief(ctx, b.ProjectID, b.ID) + switch { + case errors.Is(gerr, domain.ErrNotFound): return nil, domain.ErrNotFound + case gerr != nil: + return nil, gerr + default: + return nil, domain.ErrPreconditionFailed } - return nil, domain.ErrPreconditionFailed } return r.GetBrief(ctx, b.ProjectID, b.ID) } // Approve marks a brief approved, recording the actor. -func (r *BriefRepo) Approve(ctx context.Context, projectID, id string, by *model.Actor) (*model.CampaignBrief, error) { +func (r *BriefRepo) Approve(ctx context.Context, projectID, id string, by *model.Actor, expectedVersion int64) (*model.CampaignBrief, error) { approvedBy, err := marshalActor(by) if err != nil { return nil, err } + // Gate on version so a brief that was replaced (bumping its version) since the + // approver fetched it cannot be approved on stale content. q := `UPDATE campaign_briefs SET status='approved', approved_by=$1, approved_at=now(), version=version+1, updated_at=now() - WHERE id=$2 AND project_id=$3 AND status <> 'archived'` - tag, err := r.db.Exec(ctx, q, approvedBy, id, projectID) + WHERE id=$2 AND project_id=$3 AND version=$4 AND status <> 'archived'` + tag, err := r.db.Exec(ctx, q, approvedBy, id, projectID, expectedVersion) if err != nil { return nil, fmt.Errorf("approve brief: %w", err) } if tag.RowsAffected() == 0 { - return nil, domain.ErrNotFound + // Distinguish missing from stale version, mirroring ReplaceBrief. + _, gerr := r.GetBrief(ctx, projectID, id) + switch { + case errors.Is(gerr, domain.ErrNotFound): + return nil, domain.ErrNotFound + case gerr != nil: + return nil, gerr + default: + return nil, domain.ErrPreconditionFailed + } } return r.GetBrief(ctx, projectID, id) } diff --git a/internal/infrastructure/postgres/campaign_repo.go b/internal/infrastructure/postgres/campaign_repo.go index 9e7784a2..cf483847 100644 --- a/internal/infrastructure/postgres/campaign_repo.go +++ b/internal/infrastructure/postgres/campaign_repo.go @@ -7,6 +7,8 @@ import ( "context" "errors" "fmt" + "log/slog" + "time" "github.com/jackc/pgx/v5" @@ -19,11 +21,76 @@ type CampaignRepo struct { db *Pool } +// claimRollbackTimeout bounds the best-effort rollback of a just-inserted pending +// claim when the follow-up read fails; it runs on a context detached from the +// (possibly-cancelled) request context. +const claimRollbackTimeout = 5 * time.Second + // NewCampaignRepo returns a CampaignRepo backed by pool. func NewCampaignRepo(pool *Pool) *CampaignRepo { return &CampaignRepo{db: pool} } var _ domain.CampaignRepository = (*CampaignRepo)(nil) +// ClaimCampaignDispatch atomically claims the right to dispatch (brief, platform) +// by inserting a placeholder 'pending' campaign row. The (brief_id, platform) +// unique index makes the claim single-winner across all replicas without holding +// a connection or a blocking lock: INSERT ... ON CONFLICT DO NOTHING inserts a +// row (claimed) or does nothing (already claimed/completed). No RETURNING is used +// because ON CONFLICT DO NOTHING returns no row on conflict, so we detect the +// winner via RowsAffected and then read the current row to return it. +func (r *CampaignRepo) ClaimCampaignDispatch(ctx context.Context, projectID, briefID string, platform model.Provider, jobID string) (bool, *model.Campaign, error) { + q := `INSERT INTO campaigns (project_id, brief_id, job_id, platform, campaign_name, status) + VALUES ($1, $2, $3, $4, '', 'pending') + ON CONFLICT (brief_id, platform) DO NOTHING` + tag, err := r.db.Exec(ctx, q, projectID, briefID, jobID, string(platform)) + if err != nil { + return false, nil, fmt.Errorf("claim campaign dispatch: %w", err) + } + claimed := tag.RowsAffected() == 1 + + row, gerr := r.GetCampaignByPlatform(ctx, projectID, briefID, platform) + if gerr != nil { + // The row must exist now (we or someone else just wrote it); a read failure + // here is a genuine error. If WE just inserted the pending row, roll it back + // (best effort) so a failed claim doesn't leave a pending row that blocks the + // pair forever; report claimed=false so the caller treats it as a clean + // failure with nothing to release. + if claimed { + // Roll back on a context detached from ctx: the read likely failed + // BECAUSE ctx was cancelled, and reusing it for the DELETE would fail + // too, leaking the just-committed placeholder. + rbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), claimRollbackTimeout) + if derr := r.DeleteDispatchClaim(rbCtx, briefID, platform); derr != nil { + cancel() + // Double failure: both the post-insert read AND the rollback delete + // failed, so a 'pending' placeholder is orphaned and will block every + // future claim for this (brief, platform) — no sweeper reaps pending + // campaigns rows. This is a rare double-fault, but its blast radius is + // total for the pair, so log at ERROR with enough context to alert and + // reconcile manually (delete the stuck row) rather than swallowing it. + slog.ErrorContext(ctx, "orphaned pending campaign claim: read-after-claim AND rollback both failed; manual cleanup required", + "project_id", projectID, "brief_id", briefID, "platform", string(platform), "job_id", jobID, + "read_err", gerr.Error(), "rollback_err", derr.Error()) + return false, nil, fmt.Errorf("read campaign after claim: %w (and failed to roll back pending claim: %v)", gerr, derr) + } + cancel() + } + return false, nil, fmt.Errorf("read campaign after claim: %w", gerr) + } + return claimed, row, nil +} + +// DeleteDispatchClaim removes a still-'pending' claim row so a failed dispatch +// doesn't permanently block the (brief, platform) pair. The status guard means +// it can only ever delete a placeholder claim, never a created campaign. +func (r *CampaignRepo) DeleteDispatchClaim(ctx context.Context, briefID string, platform model.Provider) error { + q := `DELETE FROM campaigns WHERE brief_id=$1 AND platform=$2 AND status='pending'` + if _, err := r.db.Exec(ctx, q, briefID, string(platform)); err != nil { + return fmt.Errorf("delete dispatch claim: %w", err) + } + return nil +} + const campaignCols = `id::text, project_id::text, brief_id::text, job_id::text, platform, platform_campaign_id, campaign_name, status, budget_amount, budget_type, start_date, end_date, config_snapshot, result, version, created_at, updated_at` @@ -41,6 +108,24 @@ func (r *CampaignRepo) GetCampaign(ctx context.Context, projectID, briefID, id s return c, nil } +// GetCampaignByPlatform returns the campaign for a (brief, platform) pair. The +// (brief_id, platform) pair is unique, so at most one row matches. It is scoped by +// project_id for tenant isolation (defense-in-depth), matching GetCampaign and +// ClaimCampaignDispatch — brief_id is a globally-unique UUID, so this guards a +// future direct caller from reading across tenants with an attacker-influenced +// briefID. +func (r *CampaignRepo) GetCampaignByPlatform(ctx context.Context, projectID, briefID string, platform model.Provider) (*model.Campaign, error) { + q := `SELECT ` + campaignCols + ` FROM campaigns WHERE brief_id=$1 AND platform=$2 AND project_id=$3` + c, err := scanCampaign(r.db.QueryRow(ctx, q, briefID, string(platform), projectID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, domain.ErrNotFound + } + return nil, fmt.Errorf("get campaign by platform: %w", err) + } + return c, nil +} + // UpsertCampaign inserts or updates the (brief, platform) campaign row. On // conflict it updates in place (a brief change after campaigns exist). func (r *CampaignRepo) UpsertCampaign(ctx context.Context, c *model.Campaign) (*model.Campaign, error) { @@ -82,10 +167,17 @@ func (r *CampaignRepo) ReplaceCampaign(ctx context.Context, c *model.Campaign, e return nil, fmt.Errorf("replace campaign: %w", err) } if tag.RowsAffected() == 0 { - if _, gerr := r.GetCampaign(ctx, c.ProjectID, c.BriefID, c.ID); errors.Is(gerr, domain.ErrNotFound) { + // Surface a transient re-fetch error rather than masking it as a + // precondition failure, consistent with ConnectionRepo.Update. + _, gerr := r.GetCampaign(ctx, c.ProjectID, c.BriefID, c.ID) + switch { + case errors.Is(gerr, domain.ErrNotFound): return nil, domain.ErrNotFound + case gerr != nil: + return nil, gerr + default: + return nil, domain.ErrPreconditionFailed } - return nil, domain.ErrPreconditionFailed } return r.GetCampaign(ctx, c.ProjectID, c.BriefID, c.ID) } diff --git a/internal/infrastructure/postgres/job_repo.go b/internal/infrastructure/postgres/job_repo.go index 46ee880d..c3f4c221 100644 --- a/internal/infrastructure/postgres/job_repo.go +++ b/internal/infrastructure/postgres/job_repo.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/jackc/pgx/v5" @@ -14,6 +15,12 @@ import ( "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" ) +// staleJobCutoff is how long a queued/running job must have been idle (no update) +// before startup recovery treats it as orphaned. It must exceed the longest +// realistic dispatch so a job still being actively worked by another replica +// during a rolling deploy is never failed out from under it. +const staleJobCutoff = 15 * time.Minute + // JobRepo is a pgx-backed implementation of domain.JobRepository. type JobRepo struct { db *Pool @@ -26,6 +33,10 @@ var _ domain.JobRepository = (*JobRepo)(nil) const jobCols = `id::text, brief_id::text, status, result, error, created_at, updated_at, expires_at` +// jobColsPrefixed is jobCols with a `j.` table alias, for queries that JOIN +// campaign_jobs (aliased j) against campaign_briefs. +const jobColsPrefixed = `j.id::text, j.brief_id::text, j.status, j.result, j.error, j.created_at, j.updated_at, j.expires_at` + // CreateJob inserts a queued job for a brief. func (r *JobRepo) CreateJob(ctx context.Context, briefID string) (*model.CampaignJob, error) { q := `INSERT INTO campaign_jobs (brief_id) VALUES ($1) RETURNING ` + jobCols @@ -36,10 +47,90 @@ func (r *JobRepo) CreateJob(ctx context.Context, briefID string) (*model.Campaig return j, nil } +// CreateJobForApprovedBrief inserts a queued job only if the brief is still +// approved at expectedVersion, closing the approve→dispatch TOCTOU race described +// on the port: a concurrent ReplaceBrief (resets to 'draft', version+1) or +// ArchiveBrief ('archived', version+1) committing in the window must prevent the +// job from being created against the now-stale approval. +// +// Isolation reasoning — why a single guarded INSERT ... WHERE EXISTS is NOT +// enough. Under PostgreSQL's default READ COMMITTED, each statement takes a fresh +// snapshot at command start. The EXISTS subquery of a lone INSERT reads that +// snapshot; a concurrent ReplaceBrief/ArchiveBrief that COMMITS between the +// snapshot and the insert's row-visibility check is not seen by the EXISTS (it +// still sees the old approved version), so the job would be created from a +// stale-approved brief. The single-statement atomicity only rules out a commit +// interleaving WITHIN the statement's snapshot — it does not serialize against a +// mutation that commits just before the statement runs but after the caller's +// approval read. +// +// Fix: take a row lock on the brief inside ONE transaction BEFORE the insert. +// SELECT ... FOR UPDATE acquires a row-level exclusive lock on the brief row and +// re-reads its CURRENT committed state (FOR UPDATE always sees the latest +// committed row version, waiting out any in-flight writer that holds it). Any +// concurrent ReplaceBrief/ArchiveBrief UPDATEs campaign_briefs by id (see +// brief_repo.go — all three bump version on the same row), so it must acquire the +// same row lock: it either committed before our FOR UPDATE (then our re-read sees +// the bumped version and the check fails → ErrStaleApproval) or it blocks on our +// lock until this transaction commits the job (then it proceeds afterward, but +// the job was created while the brief was genuinely still approved at the read +// version, which is correct). The check-then-insert is therefore atomic with +// respect to brief mutations. Returns domain.ErrStaleApproval when the locked row +// fails the status/version check (mapped to 409). +func (r *JobRepo) CreateJobForApprovedBrief(ctx context.Context, briefID string, expectedVersion int64) (*model.CampaignJob, error) { + tx, err := r.db.Begin(ctx) + if err != nil { + return nil, fmt.Errorf("create job for approved brief: begin tx: %w", err) + } + // Roll back unless we explicitly commit. A no-op after a successful Commit + // (pgx returns ErrTxClosed, which we ignore) — this guards every error path. + defer func() { _ = tx.Rollback(ctx) }() + + // Lock the brief row and read its current committed status/version. FOR UPDATE + // serializes this against a concurrent replace/archive that touches the same + // row: whichever transaction acquires the lock first runs to completion before + // the other observes the row, so the check below cannot straddle a commit. + var ( + status string + version int64 + ) + lockQ := `SELECT status, version FROM campaign_briefs WHERE id = $1 FOR UPDATE` + if serr := tx.QueryRow(ctx, lockQ, briefID).Scan(&status, &version); serr != nil { + if errors.Is(serr, pgx.ErrNoRows) { + // The brief does not exist at all; treat it as a stale approval (there is + // nothing approved at expectedVersion to dispatch from). + return nil, domain.ErrStaleApproval + } + return nil, fmt.Errorf("create job for approved brief: lock brief: %w", serr) + } + if status != "approved" || version != expectedVersion { + // A concurrent replace/archive committed before we took the lock (or the + // brief was never approved at this version). Surface the state-conflict + // sentinel (not the generic uniqueness ErrConflict) so the service can tell + // the client to refresh and re-approve rather than reporting "already exists". + return nil, domain.ErrStaleApproval + } + + insertQ := `INSERT INTO campaign_jobs (brief_id) VALUES ($1) RETURNING ` + jobCols + j, err := scanJob(tx.QueryRow(ctx, insertQ, briefID)) + if err != nil { + return nil, fmt.Errorf("create job for approved brief: insert job: %w", err) + } + if err := tx.Commit(ctx); err != nil { + return nil, fmt.Errorf("create job for approved brief: commit: %w", err) + } + return j, nil +} + // GetJob returns a job by id. -func (r *JobRepo) GetJob(ctx context.Context, id string) (*model.CampaignJob, error) { - q := `SELECT ` + jobCols + ` FROM campaign_jobs WHERE id=$1` - j, err := scanJob(r.db.QueryRow(ctx, q, id)) +func (r *JobRepo) GetJob(ctx context.Context, projectID, id string) (*model.CampaignJob, error) { + // Scope the lookup to the caller's project by joining through the owning + // brief: a job UUID alone must not expose a job belonging to another project + // (tenant isolation — the route is /projects/{project_id}/jobs/{job_id}). + q := `SELECT ` + jobColsPrefixed + ` FROM campaign_jobs j + JOIN campaign_briefs b ON b.id = j.brief_id + WHERE j.id=$1 AND b.project_id=$2` + j, err := scanJob(r.db.QueryRow(ctx, q, id, projectID)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, domain.ErrNotFound @@ -62,6 +153,31 @@ func (r *JobRepo) UpdateJobStatus(ctx context.Context, id string, status model.J return nil } +// FailStuckJobs marks non-terminal jobs older than staleJobCutoff as failed. Run +// on startup AND periodically (see Orchestrator.StartRecoverySweeper): a +// queued/running job's dispatch goroutine lives only in the process that created +// it, so after a crash such a job would otherwise stay non-terminal forever. +// Fail-forward (rather than resume) because a partially-dispatched job cannot be +// safely re-driven without provider-side idempotency keys. +// +// The age cutoff must exceed the longest a LIVE job can go without a status +// write, or the sweep would wrongly fail an in-progress job. The orchestrator +// bounds that: a platform waits at most dispatchQueueTimeout for a slot, then +// the provider call is bounded by providerCallTimeout, then a terminal write +// follows — all well within staleJobCutoff (15m). During a rolling deploy an old +// pod can still be dispatching a recently-created job while a new pod boots, so +// only jobs idle (no update) beyond the cutoff are treated as orphaned. +func (r *JobRepo) FailStuckJobs(ctx context.Context, jobErr string) (int64, error) { + q := `UPDATE campaign_jobs SET status='failed', error=$1, updated_at=now() + WHERE status IN ('queued','running') + AND updated_at < now() - make_interval(secs => $2)` + tag, err := r.db.Exec(ctx, q, nullStr(jobErr), staleJobCutoff.Seconds()) + if err != nil { + return 0, fmt.Errorf("fail stuck jobs: %w", err) + } + return tag.RowsAffected(), nil +} + func scanJob(row pgx.Row) (*model.CampaignJob, error) { var ( j model.CampaignJob diff --git a/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.down.sql b/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.down.sql new file mode 100644 index 00000000..bd0bec1c --- /dev/null +++ b/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.down.sql @@ -0,0 +1,18 @@ +-- Copyright The Linux Foundation and each contributor to LFX. +-- SPDX-License-Identifier: MIT + +-- Revert to the 000002 schema: full uniqueness constraint and UUID project_id. +-- The UUID casts assume every stored project_id is a valid UUID; a slug-valued +-- row would fail the cast, which is the intended signal that the down migration +-- is unsafe once slug-addressed projects exist. +DROP INDEX IF EXISTS uq_campaign_briefs_project_event; + +ALTER TABLE campaigns + ALTER COLUMN project_id TYPE UUID USING project_id::uuid; + +ALTER TABLE campaign_briefs + ALTER COLUMN project_id TYPE UUID USING project_id::uuid; + +ALTER TABLE campaign_briefs + ADD CONSTRAINT campaign_briefs_project_id_event_slug_key + UNIQUE (project_id, event_slug); diff --git a/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.up.sql b/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.up.sql new file mode 100644 index 00000000..26b10a7e --- /dev/null +++ b/internal/infrastructure/postgres/migrations/000003_brief_partial_unique_slug.up.sql @@ -0,0 +1,34 @@ +-- Copyright The Linux Foundation and each contributor to LFX. +-- SPDX-License-Identifier: MIT + +-- Reconcile the campaign_briefs / campaigns schema with the rest of the service. +-- This is a SEPARATE migration rather than an edit to 000002: golang-migrate +-- records applied versions and never re-runs them, so any change to an +-- already-applied migration would silently skip databases that already ran +-- 000002. A new version reconciles both fresh and previously-migrated DBs. +-- +-- Two changes: +-- +-- 1. project_id UUID -> TEXT. The connection tables (000001) store project_id as +-- TEXT because a project is addressed by UUID *or* slug (e.g. "cncf"), and the +-- API contract advertises the same. 000002 diverged by typing it UUID, which +-- would reject slug-addressed projects. Align briefs/campaigns with TEXT. +-- +-- 2. Convert the (project_id, event_slug) uniqueness from a full table +-- constraint to a partial unique index that excludes archived rows, so +-- archiving a brief frees its (project_id, event_slug) slot for a new brief +-- (the full constraint reserved the slot permanently). Mirrors the connection +-- tables' partial-unique pattern; the index's leftmost column also covers +-- project_id-only lookups. + +ALTER TABLE campaign_briefs + DROP CONSTRAINT IF EXISTS campaign_briefs_project_id_event_slug_key; + +ALTER TABLE campaign_briefs + ALTER COLUMN project_id TYPE TEXT; + +ALTER TABLE campaigns + ALTER COLUMN project_id TYPE TEXT; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_campaign_briefs_project_event + ON campaign_briefs (project_id, event_slug) WHERE status <> 'archived'; diff --git a/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.down.sql b/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.down.sql new file mode 100644 index 00000000..48cbf3f2 --- /dev/null +++ b/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.down.sql @@ -0,0 +1,5 @@ +-- Copyright The Linux Foundation and each contributor to LFX. +-- SPDX-License-Identifier: MIT + +-- Drop the partial recovery index added in 000004.up.sql. +DROP INDEX IF EXISTS idx_campaign_jobs_recovery; diff --git a/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.up.sql b/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.up.sql new file mode 100644 index 00000000..75cb68d7 --- /dev/null +++ b/internal/infrastructure/postgres/migrations/000004_campaign_jobs_recovery_index.up.sql @@ -0,0 +1,25 @@ +-- Copyright The Linux Foundation and each contributor to LFX. +-- SPDX-License-Identifier: MIT + +-- Support the stuck-job recovery query (JobRepo.FailStuckJobs), which runs on +-- startup AND every recoverySweepInterval (5m) on EVERY replica: +-- +-- UPDATE campaign_jobs SET ... WHERE status IN ('queued','running') +-- AND updated_at < now() - ; +-- +-- 000002 only indexes brief_id, so this predicate does a full table scan of +-- campaign_jobs on every sweep. As terminal job history (succeeded/partial/ +-- failed) accumulates that scan grows unbounded, even though the live set the +-- sweep actually touches (queued/running) stays tiny. +-- +-- A PARTIAL index on updated_at, restricted to exactly the two non-terminal +-- statuses the predicate matches, keeps the index small (it never grows with +-- terminal history) and lets the sweep read only the candidate rows ordered by +-- updated_at. It is transparent to behavior — purely a performance fix. +-- +-- Separate migration (not an edit to 000002): golang-migrate records applied +-- versions and never re-runs them, so amending 000002 would silently skip DBs +-- that already ran it. +CREATE INDEX IF NOT EXISTS idx_campaign_jobs_recovery + ON campaign_jobs (updated_at) + WHERE status IN ('queued', 'running'); diff --git a/internal/middleware/inflight.go b/internal/middleware/inflight.go new file mode 100644 index 00000000..56bd5d59 --- /dev/null +++ b/internal/middleware/inflight.go @@ -0,0 +1,80 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package middleware + +import ( + "net/http" + "sync/atomic" + "time" +) + +// InflightTracker counts in-flight HTTP handler goroutines so a graceful +// shutdown can WAIT for stragglers before the database pool is closed. +// +// http.Server.Shutdown drains most handlers, but http.Server.Close (the forced +// fallback after the drain deadline) returns WITHOUT waiting for handler +// goroutines to finish — a straggler can still be executing (and touching the +// pool) after Close returns. Wrapping each request so it increments a counter +// on entry and decrements on exit lets the shutdown path wait (bounded) for +// those goroutines to actually return before the pool closes, closing the +// pool-use-after-close window that Shutdown/Close alone leave open. +type InflightTracker struct { + cnt atomic.Int64 // current in-flight handler count +} + +// NewInflightTracker returns a ready-to-use tracker. +func NewInflightTracker() *InflightTracker { return &InflightTracker{} } + +// Middleware wraps next so each in-flight request is tracked. +func (t *InflightTracker) Middleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.cnt.Add(1) + defer t.cnt.Add(-1) + next.ServeHTTP(w, r) + }) + } +} + +// inflightPollInterval is how often Wait re-checks the in-flight counter while +// bounded-waiting for stragglers to drain. +const inflightPollInterval = 5 * time.Millisecond + +// Wait blocks until all in-flight handlers have returned or the timeout elapses, +// reporting whether every handler drained (true) or the wait timed out with +// stragglers still running (false). The bounded wait guarantees a hung handler +// can never wedge shutdown past its budget; on a clean shutdown (Shutdown already +// drained the handlers) it returns immediately. A non-positive timeout never +// blocks: it reports drained iff nothing is in flight at the moment of the call. +// +// Implemented by POLLING the atomic counter rather than a WaitGroup + goroutine: +// a done-channel goroutine blocked on wg.Wait() would LEAK past a timeout (it +// stays parked until every handler finishes), and wg.Add racing wg.Wait across +// repeated calls is unsafe. Polling has no such goroutine and is safe to call +// multiple times. +func (t *InflightTracker) Wait(timeout time.Duration) bool { + if t.cnt.Load() == 0 { + return true + } + if timeout <= 0 { + return false + } + // Bound the total wait with a deadline timer and poll on a separate ticker, + // selecting on both — so a timeout SHORTER than inflightPollInterval fires on + // the deadline rather than overshooting by up to a full tick. + deadline := time.NewTimer(timeout) + defer deadline.Stop() + ticker := time.NewTicker(inflightPollInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if t.cnt.Load() == 0 { + return true + } + case <-deadline.C: + return t.cnt.Load() == 0 + } + } +} diff --git a/internal/middleware/inflight_test.go b/internal/middleware/inflight_test.go new file mode 100644 index 00000000..b73f4ebd --- /dev/null +++ b/internal/middleware/inflight_test.go @@ -0,0 +1,81 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +// Wait must block until an in-flight handler returns (so shutdown awaits a +// straggler before the pool closes), and must return true once it does. +func TestInflightTracker_WaitsForInflightHandler(t *testing.T) { + tr := NewInflightTracker() + release := make(chan struct{}) + entered := make(chan struct{}) + h := tr.Middleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + close(entered) + <-release // hold the handler in-flight + })) + + go func() { + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) + }() + <-entered // the handler is now in-flight + + // With the handler still running, a bounded Wait must time out (report false). + if tr.Wait(30 * time.Millisecond) { + t.Fatal("Wait returned true while a handler was still in-flight") + } + + close(release) // let the handler finish + if !tr.Wait(time.Second) { + t.Fatal("Wait did not observe the handler returning within the budget") + } +} + +// Wait returns immediately (true) when nothing is in flight — the clean-shutdown +// case where Shutdown already drained the handlers. +func TestInflightTracker_WaitImmediateWhenIdle(t *testing.T) { + tr := NewInflightTracker() + start := time.Now() + if !tr.Wait(time.Second) { + t.Fatal("Wait reported stragglers when idle") + } + if elapsed := time.Since(start); elapsed > 100*time.Millisecond { + t.Errorf("idle Wait took %v; should return immediately", elapsed) + } +} + +// A non-positive timeout must never block: it reports drained if idle, else +// false at once. +func TestInflightTracker_NonPositiveTimeoutDoesNotBlock(t *testing.T) { + tr := NewInflightTracker() + if !tr.Wait(0) { + t.Error("Wait(0) when idle should report drained") + } + + release := make(chan struct{}) + entered := make(chan struct{}) + h := tr.Middleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + close(entered) + <-release + })) + go func() { + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) + }() + <-entered + start := time.Now() + if tr.Wait(0) { + t.Error("Wait(0) with a handler in-flight should report not-drained") + } + if elapsed := time.Since(start); elapsed > 100*time.Millisecond { + t.Errorf("Wait(0) blocked for %v; must return at once", elapsed) + } + close(release) +} diff --git a/internal/service/brief.go b/internal/service/brief.go new file mode 100644 index 00000000..6697214e --- /dev/null +++ b/internal/service/brief.go @@ -0,0 +1,475 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package service + +import ( + "context" + "encoding/json" + "errors" + "log/slog" + "strconv" + "strings" + + briefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" + + "goa.design/goa/v3/security" +) + +// BriefService implements the generated briefs service interface, delegating to +// the brief/campaign repositories and the async orchestrator. +type BriefService struct { + briefs domain.BriefRepository + campaigns domain.CampaignRepository + jobs domain.JobRepository + orch *Orchestrator +} + +var ( + _ briefs.Service = (*BriefService)(nil) + _ briefs.Auther = (*BriefService)(nil) +) + +// NewBriefService constructs a BriefService. +func NewBriefService(b domain.BriefRepository, c domain.CampaignRepository, j domain.JobRepository, orch *Orchestrator) *BriefService { + return &BriefService{briefs: b, campaigns: c, jobs: j, orch: orch} +} + +// ensureAvailable returns the typed 503 ServiceUnavailable error when the +// service has no database wired (DATABASE_URL unset). The brief routes are still +// mounted in that mode so runtime matches the published OpenAPI contract, +// consistent with the connection service. +func (s *BriefService) ensureAvailable() error { + // Check every collaborator the service methods dereference, not just briefs: + // in the no-database mode they are all nil together, but guarding only briefs + // would nil-panic if the service were ever partially wired. + if s.briefs == nil || s.campaigns == nil || s.jobs == nil || s.orch == nil { + return &briefs.ConnServiceUnavailableError{Code: "503", Message: "brief storage is not configured"} + } + return nil +} + +// JWTAuth mirrors the connection service: it records the authenticated actor +// (validated by Heimdall at the gateway) into the context for attribution. +func (s *BriefService) JWTAuth(ctx context.Context, token string, _ *security.JWTScheme) (context.Context, error) { + if token == "" { + return ctx, &briefs.BadRequestError{Code: "400", Message: "missing bearer token"} + } + if a := actorFromToken(token); a != nil { + ctx = context.WithValue(ctx, actorCtxKey{}, a) + } + return ctx, nil +} + +// ─── Briefs ─── + +func (s *BriefService) CreateBrief(ctx context.Context, p *briefs.CreateBriefPayload) (*briefs.Brief, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + in := p.Brief + b := &model.CampaignBrief{ + ProjectID: p.ProjectID, + ProgramType: model.ProgramType(in.ProgramType), + EventSlug: in.EventSlug, + URL: strVal(in.URL), + Platforms: marshalStrings(in.Platforms), + EventDetails: marshalAny(in.EventDetails), + Copy: marshalAny(in.Copy), + Keywords: marshalAny(in.Keywords), + Targeting: marshalAny(in.Targeting), + } + created, err := s.briefs.CreateBrief(ctx, b) + if err != nil { + return nil, mapBriefErr(err) + } + // NOTE: brief/campaign lists and revision history are owned by the Query Service + // (per the api-catalog). Wiring the Query Service indexer client — so create / + // replace / approve / archive and the orchestrator's campaign upserts emit index + // events — is a deliberate follow-up (LFXV2-2665), not part of this PR. This + // persistence layer is the source of truth the indexer will later consume; no + // indexing happens here yet. + return briefResult(created), nil +} + +func (s *BriefService) GetBrief(ctx context.Context, p *briefs.GetBriefPayload) (*briefs.Brief, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + b, err := s.briefs.GetBrief(ctx, p.ProjectID, p.BriefID) + if err != nil { + return nil, mapBriefErr(err) + } + return briefResult(b), nil +} + +func (s *BriefService) UpdateBrief(ctx context.Context, p *briefs.UpdateBriefPayload) (*briefs.Brief, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + version, err := parseBriefIfMatch(p.IfMatch) + if err != nil { + return nil, err + } + in := p.Brief + b := &model.CampaignBrief{ + ID: p.BriefID, + ProjectID: p.ProjectID, + ProgramType: model.ProgramType(in.ProgramType), + EventSlug: in.EventSlug, + URL: strVal(in.URL), + Platforms: marshalStrings(in.Platforms), + EventDetails: marshalAny(in.EventDetails), + Copy: marshalAny(in.Copy), + Keywords: marshalAny(in.Keywords), + Targeting: marshalAny(in.Targeting), + } + updated, uerr := s.briefs.ReplaceBrief(ctx, b, version) + if uerr != nil { + return nil, mapBriefErr(uerr) + } + return briefResult(updated), nil +} + +func (s *BriefService) ApproveBrief(ctx context.Context, p *briefs.ApproveBriefPayload) (*briefs.Brief, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + version, err := parseBriefIfMatch(p.IfMatch) + if err != nil { + return nil, err + } + b, aerr := s.briefs.Approve(ctx, p.ProjectID, p.BriefID, actorFromCtx(ctx), version) + if aerr != nil { + return nil, mapBriefErr(aerr) + } + return briefResult(b), nil +} + +func (s *BriefService) DeleteBrief(ctx context.Context, p *briefs.DeleteBriefPayload) error { + if err := s.ensureAvailable(); err != nil { + return err + } + return mapBriefErr(s.briefs.ArchiveBrief(ctx, p.ProjectID, p.BriefID)) +} + +// ─── Campaigns ─── + +func (s *BriefService) CreateCampaigns(ctx context.Context, p *briefs.CreateCampaignsPayload) (*briefs.JobCreateResponse, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + brief, err := s.briefs.GetBrief(ctx, p.ProjectID, p.BriefID) + if err != nil { + return nil, mapBriefErr(err) + } + if brief.Status != model.BriefApproved { + return nil, &briefs.BadRequestError{Code: "400", Message: "brief must be approved before creating campaigns"} + } + if len(p.Input.Platforms) == 0 { + // Reject an empty platform set: it would create a job with zero dispatches + // that instantly aggregates to "succeeded" — a meaningless no-op job. + return nil, &briefs.BadRequestError{Code: "400", Message: "at least one platform is required"} + } + platforms := make([]model.Provider, 0, len(p.Input.Platforms)) + seen := make(map[model.Provider]struct{}, len(p.Input.Platforms)) + for _, pl := range p.Input.Platforms { + prov := model.Provider(pl) + if !prov.Valid() { + return nil, &briefs.BadRequestError{Code: "400", Message: "unknown platform: " + pl} + } + if _, dup := seen[prov]; dup { + // Reject duplicates outright: dispatching the same platform twice would + // create two paid upstream campaigns concurrently, only one of which the + // (brief_id, platform)-unique persistence can record. + return nil, &briefs.BadRequestError{Code: "400", Message: "duplicate platform: " + pl} + } + seen[prov] = struct{}{} + platforms = append(platforms, prov) + } + // Pass the version we just observed as 'approved'. Start gates job creation on + // the brief still being approved at this exact version, so a concurrent replace + // (which resets it to draft, bumping version) or archive committing between this + // read and job creation makes Start fail (domain.ErrStaleApproval → 409) rather + // than launching paid campaigns from a stale "approved" snapshot. + jobID, err := s.orch.Start(ctx, brief, brief.Version, platforms, marshalAny(p.Input.Config)) + if err != nil { + return nil, mapBriefErr(err) + } + queued := "queued" + return &briefs.JobCreateResponse{JobID: jobID, Status: queued, Platforms: p.Input.Platforms}, nil +} + +func (s *BriefService) GetCampaign(ctx context.Context, p *briefs.GetCampaignPayload) (*briefs.Campaign, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + c, err := s.campaigns.GetCampaign(ctx, p.ProjectID, p.BriefID, p.CampaignID) + if err != nil { + return nil, mapBriefErr(err) + } + return campaignResult(c), nil +} + +func (s *BriefService) UpdateCampaign(ctx context.Context, p *briefs.UpdateCampaignPayload) (*briefs.Campaign, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + version, err := parseBriefIfMatch(p.IfMatch) + if err != nil { + return nil, err + } + // Load the existing campaign and overlay only the client-editable fields + // (name, status, config). ReplaceCampaign writes every column, so budget, + // dates, platform, and result must be carried over from the stored row or a + // config-only edit would zero them out. + existing, gerr := s.campaigns.GetCampaign(ctx, p.ProjectID, p.BriefID, p.CampaignID) + if gerr != nil { + return nil, mapBriefErr(gerr) + } + existing.CampaignName = p.Campaign.CampaignName + existing.Status = p.Campaign.Status + // Only overwrite the stored config when the caller actually supplied one. + // config is optional in CampaignUpdateInput, so an omitted value must leave + // the existing ConfigSnapshot intact rather than wiping it to NULL on a + // name/status-only edit (the GET response doesn't expose config, so a client + // can't round-trip it back). + if p.Campaign.Config != nil { + existing.ConfigSnapshot = marshalAny(p.Campaign.Config) + } + updated, uerr := s.campaigns.ReplaceCampaign(ctx, existing, version) + if uerr != nil { + return nil, mapBriefErr(uerr) + } + return campaignResult(updated), nil +} + +func (s *BriefService) GetJob(ctx context.Context, p *briefs.GetJobPayload) (*briefs.JobPollResponse, error) { + if err := s.ensureAvailable(); err != nil { + return nil, err + } + j, err := s.jobs.GetJob(ctx, p.ProjectID, p.JobID) + if err != nil { + return nil, mapBriefErr(err) + } + resp := &briefs.JobPollResponse{JobID: j.ID, Status: string(j.Status)} + if len(j.Result) > 0 { + // The stored result is the orchestrator's per-platform outcome array; decode + // it into the typed response shape so the OpenAPI contract is honored. + var stored []struct { + Platform string `json:"platform"` + OK bool `json:"ok"` + CampaignID string `json:"campaign_id"` + Error string `json:"error"` + Skipped bool `json:"skipped"` + } + if err := json.Unmarshal(j.Result, &stored); err != nil { + // A persisted result that won't decode is corruption, not a valid empty + // poll response. Silently dropping it would hand back a terminal + // succeeded/partial job with NO per-platform results — an inaccurate + // response that masks the corruption as success. Surface it as a 500. + slog.ErrorContext(ctx, "failed to decode persisted job result", "job_id", j.ID, "error", err) + return nil, &briefs.InternalServerError{Code: "500", Message: "an internal server error occurred"} + } + if len(stored) == 0 && (j.Status == model.JobSucceeded || j.Status == model.JobPartial) { + // null/[] decode to an empty slice with len(j.Result) > 0, so they slip + // past the outer length guard. A succeeded/partial job is an aggregate + // over per-platform outcomes and MUST carry them; an empty decoded slice + // on such a terminal status means the stored row is corrupt. + slog.ErrorContext(ctx, "terminal job has empty per-platform results", "job_id", j.ID, "status", j.Status) + return nil, &briefs.InternalServerError{Code: "500", Message: "an internal server error occurred"} + } + resp.Result = make([]*briefs.PlatformResult, 0, len(stored)) + for _, r := range stored { + pr := &briefs.PlatformResult{Platform: r.Platform, OK: r.OK} + if r.CampaignID != "" { + id := r.CampaignID + pr.CampaignID = &id + } + switch { + case r.Skipped && !r.OK: + // A SKIPPED platform is OK=false but is NOT a failure — a concurrent + // dispatch already owns the (brief, platform) claim and is creating it. + // The orchestrator persists a skip with BOTH Skipped=true AND a raw + // internal Error string, so this case MUST be checked before the Error + // case or the friendly message below is unreachable and polling leaks + // the internal string. The generated PlatformResult has no dedicated + // "skipped" field (a Goa design change / regen, tracked in LFXV2-2665), + // so surface the deferral through Error with an explicit non-failure + // message rather than leaving an unexplained ok=false that reads as a + // silent failure. + msg := "skipped: a concurrent request already owns this platform's campaign creation (not a failure)" + pr.Error = &msg + case r.Error != "": + e := r.Error + pr.Error = &e + } + resp.Result = append(resp.Result, pr) + } + } else if j.Status == model.JobSucceeded || j.Status == model.JobPartial { + // A succeeded/partial job is an AGGREGATE over per-platform outcomes, so it + // must carry those results. An empty/absent result on such a terminal status + // means the stored row is corrupt (results lost); returning a 200 with no + // results would misrepresent corruption as a successful dispatch. A 'failed' + // job legitimately can carry only an error (e.g. a result-marshal failure), + // so it is not held to this invariant. + slog.ErrorContext(ctx, "terminal job has no per-platform results", "job_id", j.ID, "status", j.Status) + return nil, &briefs.InternalServerError{Code: "500", Message: "an internal server error occurred"} + } + if j.Error != "" { + errMsg := j.Error // copy: don't hand out a pointer aliasing the source struct field + resp.Error = &errMsg + } + return resp, nil +} + +// ─── mapping helpers ─── + +func briefResult(b *model.CampaignBrief) *briefs.Brief { + return &briefs.Brief{ + ID: b.ID, + ProjectID: b.ProjectID, + ProgramType: string(b.ProgramType), + EventSlug: b.EventSlug, + URL: optStr(b.URL), + Platforms: unmarshalStrings(b.Platforms), + EventDetails: unmarshalAny(b.EventDetails), + Copy: unmarshalAny(b.Copy), + Keywords: unmarshalAny(b.Keywords), + Targeting: unmarshalAny(b.Targeting), + Status: string(b.Status), + Version: b.Version, + Etag: optStr(briefETag(b.Version)), + } +} + +// briefETag renders the version as a quoted HTTP entity-tag (RFC 7232), e.g. +// `"3"`. parseBriefIfMatch accepts both this quoted form and a bare integer, so +// a client can round-trip the returned validator. +func briefETag(version int64) string { return `"` + strconv.FormatInt(version, 10) + `"` } + +func campaignResult(c *model.Campaign) *briefs.Campaign { + return &briefs.Campaign{ + ID: c.ID, + ProjectID: c.ProjectID, + BriefID: c.BriefID, + Platform: string(c.Platform), + PlatformCampaignID: optStr(c.PlatformCampaignID), + CampaignName: c.CampaignName, + Status: c.Status, + Version: c.Version, + Etag: optStr(briefETag(c.Version)), + } +} + +// parseBriefIfMatch converts the If-Match header to a version (428 if missing, +// 400 if non-numeric), returning briefs-package errors. +func parseBriefIfMatch(ifMatch *string) (int64, error) { + if ifMatch == nil || *ifMatch == "" { + return 0, &briefs.PreconditionRequiredError{Code: "428", Message: "an If-Match header is required"} + } + // Accept the bare version we emit and a standards-compliant STRONG quoted + // entity-tag (RFC 7232 `If-Match: "3"`). Reject a weak validator (`W/"3"`): + // RFC 7232 §3.1 requires If-Match to use the strong comparison function, so a + // weak tag must NOT authorize a write. + raw := strings.TrimSpace(*ifMatch) + if strings.HasPrefix(raw, "W/") || strings.HasPrefix(raw, "w/") { + return 0, &briefs.BadRequestError{Code: "400", Message: "If-Match must be a strong validator; weak tags (W/\"…\") are not accepted"} + } + // Strip exactly one balanced pair of surrounding quotes; reject an unbalanced + // quote (e.g. `"3` or `3"`) rather than silently accepting it as version 3. + hasOpen := strings.HasPrefix(raw, `"`) + hasClose := strings.HasSuffix(raw, `"`) + switch { + case hasOpen && hasClose && len(raw) >= 2: + raw = raw[1 : len(raw)-1] + case hasOpen || hasClose: + return 0, &briefs.BadRequestError{Code: "400", Message: "If-Match has an unbalanced quote"} + } + v, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return 0, &briefs.BadRequestError{Code: "400", Message: "If-Match must be an integer version"} + } + return v, nil +} + +func mapBriefErr(err error) error { + switch { + case err == nil: + return nil + case errors.Is(err, domain.ErrNotFound): + return &briefs.NotFoundError{Code: "404", Message: "the resource was not found"} + case errors.Is(err, domain.ErrStaleApproval): + // The approve→dispatch guard fired: the brief lost approval (or its version + // changed) between the approval read and the guarded job insert. This is a + // state conflict, not a uniqueness one — tell the client to refresh and + // re-approve, which "already exists" would misdescribe. + return &briefs.ConflictError{Code: "409", Message: "brief is no longer approved at the expected version; refresh and re-approve, then retry"} + case errors.Is(err, domain.ErrConflict): + return &briefs.ConflictError{Code: "409", Message: "the resource already exists"} + case errors.Is(err, domain.ErrPreconditionFailed): + return &briefs.PreconditionFailedError{Code: "412", Message: "the supplied ETag does not match the current version"} + } + // Preserve errors that are already typed briefs API errors (e.g. the typed + // 503 the orchestrator returns during graceful shutdown, or a 400/428/412 + // constructed upstream) so their advertised status isn't flattened to 500. + var ( + unavail *briefs.ConnServiceUnavailableError + badReq *briefs.BadRequestError + notFound *briefs.NotFoundError + conflict *briefs.ConflictError + preFailed *briefs.PreconditionFailedError + preReq *briefs.PreconditionRequiredError + ) + switch { + case errors.As(err, &unavail), errors.As(err, &badReq), errors.As(err, ¬Found), + errors.As(err, &conflict), errors.As(err, &preFailed), errors.As(err, &preReq): + return err + default: + return &briefs.InternalServerError{Code: "500", Message: "an internal server error occurred"} + } +} + +func marshalStrings(ss []string) json.RawMessage { + if len(ss) == 0 { + return nil + } + b, _ := json.Marshal(ss) + return b +} + +func unmarshalStrings(j json.RawMessage) []string { + if len(j) == 0 { + return nil + } + var ss []string + _ = json.Unmarshal(j, &ss) + return ss +} + +func marshalAny(v any) json.RawMessage { + if v == nil { + return nil + } + b, err := json.Marshal(v) + if err != nil { + return nil + } + return b +} + +// unmarshalAny decodes a stored JSONB column back into an arbitrary value for +// the response. Returns nil for empty/undecodable input so the response omits +// the field rather than surfacing malformed data. +func unmarshalAny(j json.RawMessage) any { + if len(j) == 0 { + return nil + } + var v any + if err := json.Unmarshal(j, &v); err != nil { + return nil + } + return v +} diff --git a/internal/service/brief_test.go b/internal/service/brief_test.go new file mode 100644 index 00000000..dd362a7c --- /dev/null +++ b/internal/service/brief_test.go @@ -0,0 +1,600 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package service + +import ( + "context" + "strings" + "testing" + + briefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" +) + +// fakeBriefRepo is a minimal in-memory BriefRepository for handler tests. +type fakeBriefRepo struct { + briefs map[string]*model.CampaignBrief // key: projectID|id + // onGet, when set, fires once after the next GetBrief read, modelling a + // concurrent brief mutation that commits in the approve→dispatch window. + onGet func() +} + +func newFakeBriefRepo() *fakeBriefRepo { + return &fakeBriefRepo{briefs: map[string]*model.CampaignBrief{}} +} + +func briefKey(projectID, id string) string { return projectID + "|" + id } + +func (r *fakeBriefRepo) GetBrief(_ context.Context, projectID, id string) (*model.CampaignBrief, error) { + b, ok := r.briefs[briefKey(projectID, id)] + if !ok { + return nil, domain.ErrNotFound + } + // Return a snapshot copy so a caller holding the result observes the state as + // of the read, even if the stored brief is subsequently mutated (used to model + // a concurrent replace committing in the approve→dispatch window). + cp := *b + // onGet, if set, fires after the read to simulate a concurrent mutation + // committing between this read and a later guarded write. + if r.onGet != nil { + hook := r.onGet + r.onGet = nil // one-shot + hook() + } + return &cp, nil +} + +func (r *fakeBriefRepo) CreateBrief(_ context.Context, b *model.CampaignBrief) (*model.CampaignBrief, error) { + b.ID = "b-new" + b.Version = 1 + r.briefs[briefKey(b.ProjectID, b.ID)] = b + return b, nil +} + +func (r *fakeBriefRepo) ReplaceBrief(_ context.Context, b *model.CampaignBrief, _ int64) (*model.CampaignBrief, error) { + r.briefs[briefKey(b.ProjectID, b.ID)] = b + return b, nil +} + +func (r *fakeBriefRepo) Approve(_ context.Context, projectID, id string, _ *model.Actor, expectedVersion int64) (*model.CampaignBrief, error) { + b, ok := r.briefs[briefKey(projectID, id)] + if !ok { + return nil, domain.ErrNotFound + } + if b.Version != expectedVersion { + return nil, domain.ErrPreconditionFailed + } + b.Status = model.BriefApproved + return b, nil +} + +func (r *fakeBriefRepo) ArchiveBrief(_ context.Context, projectID, id string) error { + if _, ok := r.briefs[briefKey(projectID, id)]; !ok { + return domain.ErrNotFound + } + return nil +} + +// A BriefService built with nil repos (DATABASE_URL unset) must return the typed +// 503 ServiceUnavailable for every route rather than panicking on a nil repo, so +// runtime matches the published OpenAPI contract (mirrors the connection service). +func TestBriefService_NilRepo_ReturnsServiceUnavailable(t *testing.T) { + s := NewBriefService(nil, nil, nil, nil) + ctx := context.Background() + + if _, err := s.GetBrief(ctx, &briefs.GetBriefPayload{ProjectID: "cncf", BriefID: "b1"}); !isBriefUnavailable(err) { + t.Errorf("GetBrief: expected *briefs.ConnServiceUnavailableError, got %T (%v)", err, err) + } + if _, err := s.CreateBrief(ctx, &briefs.CreateBriefPayload{ProjectID: "cncf", Brief: &briefs.BriefInput{}}); !isBriefUnavailable(err) { + t.Errorf("CreateBrief: expected *briefs.ConnServiceUnavailableError, got %T (%v)", err, err) + } + if _, err := s.GetJob(ctx, &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}); !isBriefUnavailable(err) { + t.Errorf("GetJob: expected *briefs.ConnServiceUnavailableError, got %T (%v)", err, err) + } + if err := s.DeleteBrief(ctx, &briefs.DeleteBriefPayload{ProjectID: "cncf", BriefID: "b1"}); !isBriefUnavailable(err) { + t.Errorf("DeleteBrief: expected *briefs.ConnServiceUnavailableError, got %T (%v)", err, err) + } +} + +// A missing bearer token is a client-side problem and must map to 400, not 500 +// (a 500 misrepresents it as a server fault and can trigger ops alerting). +func TestBriefService_JWTAuth_EmptyTokenIsBadRequest(t *testing.T) { + s := NewBriefService(nil, nil, nil, nil) + _, err := s.JWTAuth(context.Background(), "", nil) + if _, ok := err.(*briefs.BadRequestError); !ok { + t.Fatalf("expected *briefs.BadRequestError for empty token, got %T (%v)", err, err) + } +} + +func isBriefUnavailable(err error) bool { + _, ok := err.(*briefs.ConnServiceUnavailableError) + return ok +} + +func newTestBriefService(repo *fakeBriefRepo) *BriefService { + camps := &fakeCampaignRepo{} + jobs := newFakeJobRepo() + orch := NewOrchestrator(camps, jobs, nil) + return NewBriefService(repo, camps, jobs, orch) +} + +func TestBriefService_CreateAndGet_HappyPath(t *testing.T) { + repo := newFakeBriefRepo() + s := newTestBriefService(repo) + created, err := s.CreateBrief(context.Background(), &briefs.CreateBriefPayload{ + ProjectID: "cncf", + Brief: &briefs.BriefInput{ProgramType: "events", EventSlug: "kubecon-2025"}, + }) + if err != nil { + t.Fatalf("CreateBrief: %v", err) + } + got, err := s.GetBrief(context.Background(), &briefs.GetBriefPayload{ProjectID: "cncf", BriefID: created.ID}) + if err != nil { + t.Fatalf("GetBrief: %v", err) + } + if got.EventSlug != "kubecon-2025" { + t.Errorf("event_slug = %q, want kubecon-2025", got.EventSlug) + } +} + +// CreateCampaigns must reject a brief that has not been approved (400), the +// approval-gate invariant from the architecture (a brief must be approved +// before campaigns can be created from it). +func TestBriefService_CreateCampaigns_RejectsUnapprovedBrief(t *testing.T) { + repo := newFakeBriefRepo() + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefDraft, + } + s := newTestBriefService(repo) + _, err := s.CreateCampaigns(context.Background(), &briefs.CreateCampaignsPayload{ + ProjectID: "cncf", BriefID: "b1", + Input: &briefs.CampaignCreateInput{Platforms: []string{"google-ads"}}, + }) + if _, ok := err.(*briefs.BadRequestError); !ok { + t.Fatalf("expected *briefs.BadRequestError for unapproved brief, got %T (%v)", err, err) + } +} + +// CreateCampaigns must reject an empty platform set (400) rather than creating a +// no-op job that instantly aggregates to succeeded. +func TestBriefService_CreateCampaigns_RejectsEmptyPlatforms(t *testing.T) { + repo := newFakeBriefRepo() + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefApproved, + } + s := newTestBriefService(repo) + _, err := s.CreateCampaigns(context.Background(), &briefs.CreateCampaignsPayload{ + ProjectID: "cncf", BriefID: "b1", + Input: &briefs.CampaignCreateInput{Platforms: []string{}}, + }) + if _, ok := err.(*briefs.BadRequestError); !ok { + t.Fatalf("expected *briefs.BadRequestError for empty platforms, got %T (%v)", err, err) + } +} + +// CreateCampaigns must reject a duplicate platform (400) rather than dispatching +// the same platform twice, which would create two paid upstream campaigns. +func TestBriefService_CreateCampaigns_RejectsDuplicatePlatforms(t *testing.T) { + repo := newFakeBriefRepo() + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefApproved, + } + s := newTestBriefService(repo) + _, err := s.CreateCampaigns(context.Background(), &briefs.CreateCampaignsPayload{ + ProjectID: "cncf", BriefID: "b1", + Input: &briefs.CampaignCreateInput{Platforms: []string{"google-ads", "google-ads"}}, + }) + if _, ok := err.(*briefs.BadRequestError); !ok { + t.Fatalf("expected *briefs.BadRequestError for duplicate platforms, got %T (%v)", err, err) + } +} + +// Create/Get must round-trip the full brief content (event_details, copy, +// keywords, targeting), not drop it from the response. +func TestBriefService_ResponseIncludesBriefContent(t *testing.T) { + repo := newFakeBriefRepo() + s := newTestBriefService(repo) + details := map[string]any{"venue": "Salt Lake City"} + kw := []any{"kubernetes", "cloud native"} + created, err := s.CreateBrief(context.Background(), &briefs.CreateBriefPayload{ + ProjectID: "cncf", + Brief: &briefs.BriefInput{ + ProgramType: "events", + EventSlug: "kubecon-2025", + EventDetails: details, + Keywords: kw, + }, + }) + if err != nil { + t.Fatalf("CreateBrief: %v", err) + } + if created.EventDetails == nil { + t.Error("create response dropped event_details") + } + got, err := s.GetBrief(context.Background(), &briefs.GetBriefPayload{ProjectID: "cncf", BriefID: created.ID}) + if err != nil { + t.Fatalf("GetBrief: %v", err) + } + if got.EventDetails == nil { + t.Error("get response dropped event_details") + } + if got.Keywords == nil { + t.Error("get response dropped keywords") + } +} + +// ApproveBrief requires an If-Match and is gated on version, so a brief that was +// replaced since the approver fetched it cannot be approved on stale content. +func TestBriefService_ApproveBrief_VersionGated(t *testing.T) { + repo := newFakeBriefRepo() + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefDraft, Version: 3, + } + s := newTestBriefService(repo) + + // Missing If-Match -> 428 PreconditionRequired. + if _, err := s.ApproveBrief(context.Background(), &briefs.ApproveBriefPayload{ProjectID: "cncf", BriefID: "b1"}); err == nil { + t.Fatal("expected an error when If-Match is missing") + } else if _, ok := err.(*briefs.PreconditionRequiredError); !ok { + t.Fatalf("missing If-Match: got %T (%v), want *PreconditionRequiredError", err, err) + } + + // Stale version -> 412 PreconditionFailed. + stale := "2" + if _, err := s.ApproveBrief(context.Background(), &briefs.ApproveBriefPayload{ProjectID: "cncf", BriefID: "b1", IfMatch: &stale}); err == nil { + t.Fatal("expected an error approving a stale version") + } else if _, ok := err.(*briefs.PreconditionFailedError); !ok { + t.Fatalf("stale version: got %T (%v), want *PreconditionFailedError", err, err) + } + + // Current version -> approved. + cur := "3" + got, err := s.ApproveBrief(context.Background(), &briefs.ApproveBriefPayload{ProjectID: "cncf", BriefID: "b1", IfMatch: &cur}) + if err != nil { + t.Fatalf("approve at current version: %v", err) + } + if got.Status != "approved" { + t.Errorf("status = %q, want approved", got.Status) + } +} + +// parseBriefIfMatch accepts a bare version and a strong quoted entity-tag, and +// rejects non-numeric input AND weak validators (If-Match requires strong +// comparison per RFC 7232). +func TestParseBriefIfMatch_AcceptsQuotedETag(t *testing.T) { + cases := map[string]int64{`3`: 3, `"3"`: 3, ` "42" `: 42} + for in, want := range cases { + v, err := parseBriefIfMatch(&in) + if err != nil { + t.Errorf("parseBriefIfMatch(%q) error: %v", in, err) + continue + } + if v != want { + t.Errorf("parseBriefIfMatch(%q) = %d, want %d", in, v, want) + } + } + for _, weak := range []string{`W/"3"`, `w/"3"`} { + if _, err := parseBriefIfMatch(&weak); err == nil { + t.Errorf("parseBriefIfMatch(%q) = nil error, want weak-tag rejection", weak) + } + } + bad := `abc` + if _, err := parseBriefIfMatch(&bad); err == nil { + t.Errorf("parseBriefIfMatch(%q) = nil error, want BadRequest", bad) + } + var nilp *string + if _, err := parseBriefIfMatch(nilp); err == nil { + t.Error("parseBriefIfMatch(nil) = nil error, want PreconditionRequired") + } +} + +// versionGuardedJobRepo models the atomic approve-re-check that +// CreateJobForApprovedBrief performs in SQL: it creates a job only if the brief +// in the shared store is still 'approved' at the expected version. It lets a test +// exercise the approve→dispatch TOCTOU guard without a real database. +type versionGuardedJobRepo struct { + fakeJobRepo + store *fakeBriefRepo +} + +func (r *versionGuardedJobRepo) CreateJobForApprovedBrief(_ context.Context, briefID string, expectedVersion int64) (*model.CampaignJob, error) { + // Re-verify approval atomically with the create, mirroring the real repo's + // SELECT ... FOR UPDATE re-check inside the job-insert transaction: any + // concurrent replace/archive that bumped the version or changed the status + // fails the guard. + var stillApproved bool + for _, b := range r.store.briefs { + if b.ID == briefID && b.Status == model.BriefApproved && b.Version == expectedVersion { + stillApproved = true + break + } + } + if !stillApproved { + // Mirror the real repo: the approve→dispatch guard fires with the + // state-conflict sentinel (not the generic uniqueness ErrConflict). + return nil, domain.ErrStaleApproval + } + r.mu.Lock() + defer r.mu.Unlock() + return r.createLocked(briefID) +} + +// TestBriefService_CreateCampaigns_TOCTOURaceFailsClosed verifies the +// approve→dispatch race is closed: if a concurrent replace resets the brief to +// draft (and bumps its version) AFTER CreateCampaigns reads it as approved but +// BEFORE the job is created, the request FAILS (409 conflict) rather than +// launching paid campaigns from the stale "approved" snapshot. +func TestBriefService_CreateCampaigns_TOCTOURaceFailsClosed(t *testing.T) { + repo := newFakeBriefRepo() + // The brief is approved at version 5 when CreateCampaigns reads it. + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefApproved, Version: 5, + } + camps := &fakeCampaignRepo{} + jobs := &versionGuardedJobRepo{ + fakeJobRepo: fakeJobRepo{jobs: map[string]*model.CampaignJob{}}, + store: repo, + } + orch := NewOrchestrator(camps, jobs, nil) + s := NewBriefService(repo, camps, jobs, orch) + + // Model the concurrent replace committing in the window: it fires AFTER + // CreateCampaigns reads the brief as approved@v5 (GetBrief returns a snapshot) + // but BEFORE the guarded job create re-checks the store. It resets the brief to + // draft and bumps the version, exactly as ReplaceBrief does, so the guarded + // create must now fail (409) rather than dispatch from the stale snapshot. + repo.onGet = func() { + repo.briefs[briefKey("cncf", "b1")].Status = model.BriefDraft + repo.briefs[briefKey("cncf", "b1")].Version = 6 + } + + _, err := s.CreateCampaigns(context.Background(), &briefs.CreateCampaignsPayload{ + ProjectID: "cncf", BriefID: "b1", + Input: &briefs.CampaignCreateInput{Platforms: []string{"google-ads"}}, + }) + ce, ok := err.(*briefs.ConflictError) + if !ok { + t.Fatalf("expected *briefs.ConflictError (concurrent replace closed the TOCTOU race), got %T (%v)", err, err) + } + // The message must describe the version/approval conflict accurately (refresh + // and re-approve), NOT the misleading uniqueness "already exists" — a client + // needs to know to re-approve and retry. + if !strings.Contains(ce.Message, "no longer approved") || !strings.Contains(ce.Message, "re-approve") { + t.Errorf("conflict message = %q, want it to describe the stale-approval remedy (re-approve and retry)", ce.Message) + } + if strings.Contains(ce.Message, "already exists") { + t.Errorf("conflict message = %q, must not misdescribe a version conflict as a uniqueness one", ce.Message) + } + if len(jobs.jobs) != 0 { + t.Errorf("a job was created despite the brief no longer being approved: %d jobs", len(jobs.jobs)) + } +} + +// TestBriefService_CreateCampaigns_ApprovedAtVersionSucceeds verifies the guard +// does not over-reject: when the brief is still approved at the read version, the +// job is created normally. +func TestBriefService_CreateCampaigns_ApprovedAtVersionSucceeds(t *testing.T) { + repo := newFakeBriefRepo() + repo.briefs[briefKey("cncf", "b1")] = &model.CampaignBrief{ + ID: "b1", ProjectID: "cncf", Status: model.BriefApproved, Version: 5, + } + camps := &fakeCampaignRepo{} + jobs := &versionGuardedJobRepo{ + fakeJobRepo: fakeJobRepo{jobs: map[string]*model.CampaignJob{}}, + store: repo, + } + orch := NewOrchestrator(camps, jobs, nil) + s := NewBriefService(repo, camps, jobs, orch) + + resp, err := s.CreateCampaigns(context.Background(), &briefs.CreateCampaignsPayload{ + ProjectID: "cncf", BriefID: "b1", + Input: &briefs.CampaignCreateInput{Platforms: []string{"google-ads"}}, + }) + if err != nil { + t.Fatalf("CreateCampaigns: %v", err) + } + if resp.JobID == "" { + t.Error("expected a job id for a brief still approved at the read version") + } +} + +// campaignEditRepo is a minimal CampaignRepository for UpdateCampaign tests. +type campaignEditRepo struct { + got *model.Campaign // the campaign passed to ReplaceCampaign + cur *model.Campaign // the stored campaign returned by GetCampaign +} + +func (r *campaignEditRepo) GetCampaign(context.Context, string, string, string) (*model.Campaign, error) { + cp := *r.cur + return &cp, nil +} +func (r *campaignEditRepo) GetCampaignByPlatform(context.Context, string, string, model.Provider) (*model.Campaign, error) { + return nil, domain.ErrNotFound +} +func (r *campaignEditRepo) ClaimCampaignDispatch(context.Context, string, string, model.Provider, string) (bool, *model.Campaign, error) { + return true, nil, nil +} +func (r *campaignEditRepo) DeleteDispatchClaim(context.Context, string, model.Provider) error { + return nil +} +func (r *campaignEditRepo) UpsertCampaign(_ context.Context, c *model.Campaign) (*model.Campaign, error) { + return c, nil +} +func (r *campaignEditRepo) ReplaceCampaign(_ context.Context, c *model.Campaign, _ int64) (*model.Campaign, error) { + r.got = c + return c, nil +} + +// UpdateCampaign must NOT wipe the stored config when the caller omits config. +func TestBriefService_UpdateCampaign_PreservesConfigWhenOmitted(t *testing.T) { + camps := &campaignEditRepo{cur: &model.Campaign{ + ID: "c1", ProjectID: "cncf", BriefID: "b1", Version: 2, + CampaignName: "old", Status: "active", + ConfigSnapshot: []byte(`{"budget":100}`), + }} + s := &BriefService{briefs: &fakeBriefRepo{briefs: map[string]*model.CampaignBrief{}}, campaigns: camps, jobs: newFakeJobRepo(), orch: NewOrchestrator(camps, newFakeJobRepo(), nil)} + v := "2" + _, err := s.UpdateCampaign(context.Background(), &briefs.UpdateCampaignPayload{ + ProjectID: "cncf", BriefID: "b1", CampaignID: "c1", IfMatch: &v, + Campaign: &briefs.CampaignUpdateInput{CampaignName: "new", Status: "paused"}, // Config omitted + }) + if err != nil { + t.Fatalf("UpdateCampaign: %v", err) + } + if string(camps.got.ConfigSnapshot) != `{"budget":100}` { + t.Errorf("config was overwritten: %s, want the stored {\"budget\":100}", camps.got.ConfigSnapshot) + } + if camps.got.CampaignName != "new" || camps.got.Status != "paused" { + t.Errorf("name/status not applied: %q/%q", camps.got.CampaignName, camps.got.Status) + } +} + +// getJobTestService builds a BriefService whose job repo returns the given job +// verbatim from GetJob, for exercising the poll-response decoding path. +func getJobTestService(job *model.CampaignJob) *BriefService { + repo := newFakeBriefRepo() + camps := &fakeCampaignRepo{} + jobs := newFakeJobRepo() + jobs.jobs[job.ID] = job + orch := NewOrchestrator(camps, jobs, nil) + return NewBriefService(repo, camps, jobs, orch) +} + +// GetJob must NOT silently discard a persisted result that won't decode: a +// malformed result on a terminal succeeded/partial job would otherwise return a +// 200 poll response with NO per-platform results, masking corruption as success. +// It must surface a 500 InternalServerError instead. +func TestBriefService_GetJob_MalformedResultIsInternalError(t *testing.T) { + s := getJobTestService(&model.CampaignJob{ + ID: "j1", BriefID: "b1", Status: model.JobSucceeded, + Result: []byte(`{"not":"an array"}`), // valid JSON, wrong shape → unmarshal error + }) + _, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if _, ok := err.(*briefs.InternalServerError); !ok { + t.Fatalf("expected *briefs.InternalServerError for a malformed job result, got %T (%v)", err, err) + } +} + +// A terminal succeeded/partial job is an aggregate over per-platform outcomes, so +// an empty/absent result on that status means the row is corrupt. GetJob must +// surface a 500 rather than a 200 with no results (which would misrepresent +// corruption as a successful dispatch). +func TestBriefService_GetJob_TerminalWithoutResultsIsInternalError(t *testing.T) { + for _, st := range []model.JobStatus{model.JobSucceeded, model.JobPartial} { + s := getJobTestService(&model.CampaignJob{ID: "j1", BriefID: "b1", Status: st, Result: nil}) + _, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if _, ok := err.(*briefs.InternalServerError); !ok { + t.Fatalf("status %s: expected *briefs.InternalServerError for a terminal job with no results, got %T (%v)", st, err, err) + } + } +} + +// A succeeded/partial job whose stored Result is the raw JSON null or [] decodes +// to an EMPTY slice with len(j.Result) > 0, so it slips past the outer length +// guard. GetJob must still treat this as corruption on a terminal-aggregate status +// and surface a 500, not a 200 with zero per-platform results. +func TestBriefService_GetJob_EmptyDecodedResultIsInternalError(t *testing.T) { + for _, raw := range []string{`null`, `[]`} { + for _, st := range []model.JobStatus{model.JobSucceeded, model.JobPartial} { + s := getJobTestService(&model.CampaignJob{ + ID: "j1", BriefID: "b1", Status: st, Result: []byte(raw), + }) + _, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if _, ok := err.(*briefs.InternalServerError); !ok { + t.Fatalf("raw %q status %s: expected *briefs.InternalServerError for an empty decoded result on a terminal job, got %T (%v)", raw, st, err, err) + } + } + } +} + +// The orchestrator persists a skipped platform with BOTH skipped=true AND a raw +// internal error string ("skipped: another concurrent dispatch owns..."). GetJob +// must check the skipped case before the error case so it surfaces the explicit +// "not a failure" message rather than leaking the internal string. +func TestBriefService_GetJob_SkippedWithInternalErrorSurfacesNonFailure(t *testing.T) { + s := getJobTestService(&model.CampaignJob{ + ID: "j1", BriefID: "b1", Status: model.JobSucceeded, + Result: []byte(`[{"platform":"google-ads","ok":true,"campaign_id":"pc-1"},{"platform":"linkedin-ads","ok":false,"skipped":true,"error":"skipped: another concurrent dispatch owns this platform"}]`), + }) + resp, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if err != nil { + t.Fatalf("GetJob: %v", err) + } + var li *briefs.PlatformResult + for _, r := range resp.Result { + if r.Platform == "linkedin-ads" { + li = r + } + } + if li == nil { + t.Fatal("linkedin-ads result missing") + } + if li.Error == nil || !strings.Contains(*li.Error, "not a failure") { + t.Errorf("skipped platform must surface the friendly non-failure message, got %v", li.Error) + } + if li.Error != nil && strings.Contains(*li.Error, "another concurrent dispatch owns") { + t.Errorf("skipped platform leaked the internal error string: %v", *li.Error) + } +} + +// A valid persisted result decodes into typed per-platform results, and a failed +// job carrying only an error (no results — a legitimate finalize-marshal-failure +// outcome) is NOT treated as corruption: it returns 200 with the error surfaced. +func TestBriefService_GetJob_ValidResultsAndFailedErrorOnly(t *testing.T) { + // Valid result on a succeeded job round-trips into typed results. + s := getJobTestService(&model.CampaignJob{ + ID: "j1", BriefID: "b1", Status: model.JobSucceeded, + Result: []byte(`[{"platform":"google-ads","ok":true,"campaign_id":"pc-1"}]`), + }) + resp, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if err != nil { + t.Fatalf("GetJob (valid result): %v", err) + } + if len(resp.Result) != 1 || resp.Result[0].Platform != "google-ads" || !resp.Result[0].OK { + t.Errorf("decoded result = %+v, want one ok google-ads result", resp.Result) + } + + // A failed job with only an error message (no results) is a legitimate outcome + // (e.g. the finalize marshal failed), not corruption → 200 with the error. + s2 := getJobTestService(&model.CampaignJob{ + ID: "j2", BriefID: "b1", Status: model.JobFailed, Result: nil, Error: "failed to serialize job result", + }) + resp2, err := s2.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j2"}) + if err != nil { + t.Fatalf("GetJob (failed, error-only): %v", err) + } + if resp2.Error == nil || *resp2.Error != "failed to serialize job result" { + t.Errorf("failed job error = %v, want the surfaced error message", resp2.Error) + } +} + +// TestBriefService_GetJob_SkippedSurfacesNonFailure verifies a skipped platform +// (ok=false, skipped=true) on a succeeded job is surfaced with an explicit +// non-failure message rather than an unexplained ok=false that reads as a failure. +func TestBriefService_GetJob_SkippedSurfacesNonFailure(t *testing.T) { + s := getJobTestService(&model.CampaignJob{ + ID: "j1", BriefID: "b1", Status: model.JobSucceeded, + Result: []byte(`[{"platform":"google-ads","ok":true,"campaign_id":"pc-1"},{"platform":"linkedin-ads","ok":false,"skipped":true}]`), + }) + resp, err := s.GetJob(context.Background(), &briefs.GetJobPayload{ProjectID: "cncf", JobID: "j1"}) + if err != nil { + t.Fatalf("GetJob: %v", err) + } + var li *briefs.PlatformResult + for _, r := range resp.Result { + if r.Platform == "linkedin-ads" { + li = r + } + } + if li == nil { + t.Fatal("linkedin-ads result missing") + } + if li.OK { + t.Errorf("skipped platform OK = true, want false") + } + if li.Error == nil || !strings.Contains(*li.Error, "skipped") || !strings.Contains(*li.Error, "not a failure") { + t.Errorf("skipped platform must surface a non-failure 'skipped' message, got %v", li.Error) + } +} diff --git a/internal/service/orchestrator.go b/internal/service/orchestrator.go new file mode 100644 index 00000000..e26b4ef5 --- /dev/null +++ b/internal/service/orchestrator.go @@ -0,0 +1,697 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package service + +import ( + "context" + "encoding/json" + "errors" + "log/slog" + "sync" + "time" + + "golang.org/x/sync/errgroup" + + briefs "github.com/linuxfoundation/lfx-v2-campaign-service/gen/lfx_v2_campaign_service_briefs" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" +) + +// maxParallelDispatch bounds concurrent per-platform campaign creation. +const maxParallelDispatch = 5 + +// CancelGracePeriod is how long Shutdown waits, after cancelling in-flight runs +// on a drain timeout, for them to unwind before it returns (and the pool closes). +// A run cancelled at the drain deadline may, in its worst case, still owe TWO +// detached writes that must complete before the pool closes: persisting a +// just-created upstream campaign (persistResultTimeout) and then writing the +// terminal job status (jobFinalizeTimeout). Sizing the grace to cover both (plus a +// second of slack) keeps the documented invariant honest — both detached writes +// fit inside the grace window rather than racing the pool close. (In practice both +// are single-row statements that finish in milliseconds; the ceilings only bound a +// pathological hang.) +const CancelGracePeriod = jobFinalizeTimeout + persistResultTimeout + time.Second + +// claimReleaseTimeout bounds the best-effort pending-claim cleanup, which runs on +// a context detached from the (possibly-cancelled) dispatch context. +const claimReleaseTimeout = 5 * time.Second + +// dispatchQueueTimeout bounds how long a platform waits for a semaphore slot +// before it's recorded as failed. Without it, a large backlog could keep a job +// queued longer than staleJobCutoff, so the recovery sweep would wrongly fail a +// still-live job. Kept comfortably below staleJobCutoff (15m) even added to +// providerCallTimeout and the finalize write, so a job that is actually +// progressing always reaches a terminal state before it could look stuck. +const dispatchQueueTimeout = 10 * time.Minute + +// providerCallTimeout bounds a single provider Dispatch call. The dispatch +// context is otherwise only cancelled at shutdown, so a provider call that hangs +// (unresponsive upstream, dropped connection with no client timeout) would leave +// its job "running" forever and permanently occupy one of the maxParallelDispatch +// semaphore slots. This ceiling guarantees the slot and job are released. It is +// generous: real ad-platform create flows are multi-request but complete in well +// under a minute. +const providerCallTimeout = 2 * time.Minute + +// jobFinalizeTimeout bounds the terminal job-status write, which runs on a +// context detached from the dispatch context so a cancelled run still reaches a +// terminal state instead of being stuck queued/running. +const jobFinalizeTimeout = 10 * time.Second + +// persistResultTimeout bounds the post-provider persistence upsert that records a +// successfully-created upstream campaign. Like the finalize write it runs on a +// context DETACHED from the dispatch context: once the provider has created the +// paid resource upstream, persisting its id must not be abandoned merely because +// Shutdown cancelled the dispatch context — that would lose the record of a +// campaign that WAS created (an unreconcilable orphan). It is kept well below +// CancelGracePeriod so it completes within the shutdown grace window and can never +// itself hang shutdown. Kept modest (and below jobFinalizeTimeout) so the sum of +// both detached writes fits within CancelGracePeriod without pushing +// ContainerCloseTimeout past the overall shutdown budget (asserted in container +// init()). +const persistResultTimeout = 5 * time.Second + +// PlatformDispatcher creates a campaign on one ad platform. Implementations are +// the per-provider adapters (added as those integrations land); the +// orchestrator is agnostic to them. +type PlatformDispatcher interface { + // Dispatch creates a campaign on the platform and returns the resulting + // campaign row (platform_campaign_id, status, result populated). + Dispatch(ctx context.Context, brief *model.CampaignBrief, platform model.Provider, config json.RawMessage) (*model.Campaign, error) +} + +// noUpstreamCreator lets a dispatcher signal that a returned error occurred +// BEFORE any upstream (paid) create call — e.g. input validation or config +// errors — so the orchestrator can safely release the claim and allow a retry. +// A plain error (which might follow a timeout that did create a campaign) is +// treated conservatively: the claim is retained to prevent a duplicate. +type noUpstreamCreator interface{ NoUpstreamCreate() bool } + +// dispatchErrIsPreCreate reports whether a dispatcher error is known to have +// occurred before any upstream create (safe to release the claim). +func dispatchErrIsPreCreate(err error) bool { + var n noUpstreamCreator + return errors.As(err, &n) && n.NoUpstreamCreate() +} + +// Orchestrator runs async multi-platform campaign creation for a brief. +type Orchestrator struct { + campaigns domain.CampaignRepository + jobs domain.JobRepository + dispatchers map[model.Provider]PlatformDispatcher + + // wg tracks in-flight dispatch runs so Shutdown can wait for them before the + // process (and the DB pool) goes away. mu guards the shutting-down flag so a + // Start racing with Shutdown either registers on wg or is rejected, never + // launches an untracked goroutine after Shutdown has stopped waiting. + // rootCtx/rootCancel parent every dispatch run so Shutdown can cancel them if + // the drain deadline expires (rather than leaving them running against a + // closing pool). + mu sync.Mutex + wg sync.WaitGroup + draining bool + rootCtx context.Context + rootCancel context.CancelFunc + // sweeperCtx / sweeperCancel own the background recovery sweeper's lifetime, + // SEPARATELY from rootCtx (which owns dispatch runs). Cancelled once, guarded + // by sweeperOnce, at the very START of Shutdown — before the dispatch drain — + // so: + // 1. A sweep already blocked in the DB is interrupted PROMPTLY (its query + // derives from sweeperCtx, so cancelling it aborts the statement rather + // than waiting for it to return on its own). + // 2. The sweeper's own timeout/cancellation is spent up front and can never + // consume any of the dispatch-drain budget — the drain phase (bounded by + // drainTimeout) and the sweeper shutdown do not compete for a deadline, + // so a maintenance query can't starve healthy in-flight dispatches. + // The sweeper is still tracked by o.wg, so Shutdown waits for it to return + // before the caller closes the pool; but because it's cancelled first, that + // wait is effectively instantaneous and does not eat into the drain window. + sweeperCtx context.Context + sweeperCancel context.CancelFunc + sweeperOnce sync.Once + // sem is a process-wide semaphore bounding concurrent provider dispatches + // across ALL jobs (a per-job errgroup limit would let N concurrent jobs each + // get maxParallelDispatch slots, leaving total provider calls unbounded). + sem chan struct{} +} + +// NewOrchestrator constructs an Orchestrator. dispatchers may be empty; a +// platform with no registered dispatcher is recorded as a failed result. +func NewOrchestrator(campaigns domain.CampaignRepository, jobs domain.JobRepository, dispatchers map[model.Provider]PlatformDispatcher) *Orchestrator { + if dispatchers == nil { + dispatchers = map[model.Provider]PlatformDispatcher{} + } + rootCtx, rootCancel := context.WithCancel(context.Background()) + sweeperCtx, sweeperCancel := context.WithCancel(context.Background()) + return &Orchestrator{ + campaigns: campaigns, + jobs: jobs, + dispatchers: dispatchers, + rootCtx: rootCtx, + rootCancel: rootCancel, + sweeperCtx: sweeperCtx, + sweeperCancel: sweeperCancel, + sem: make(chan struct{}, maxParallelDispatch), + } +} + +// recoverySweepInterval is how often the background sweeper re-scans for stuck +// jobs. The startup scan alone can't recover a job orphaned by a crash less than +// staleJobCutoff ago (it's too young to be considered stuck at boot and is never +// re-examined); a periodic sweep eventually catches it. Kept well below +// staleJobCutoff so a newly-stuck job is picked up within roughly one cutoff +// window rather than only on the next restart. +const recoverySweepInterval = 5 * time.Minute + +// StartRecoverySweeper launches a background goroutine that periodically fails +// jobs stuck past staleJobCutoff, complementing the one-time startup scan so a +// job orphaned by a crash younger than the cutoff is still eventually recovered. +// +// The goroutine is tracked by wg (so Shutdown waits for it before the pool +// closes) but its lifetime is owned by sweeperCtx, NOT rootCtx: Shutdown cancels +// sweeperCtx first, before draining dispatch runs. Because the in-flight sweep's +// query derives from sweeperCtx, cancelling it interrupts a sweep already +// blocked in the DB promptly, and it does so up front so the sweeper's own +// shutdown never competes with the dispatch-drain budget. Call once after +// construction. +func (o *Orchestrator) StartRecoverySweeper() { + o.wg.Add(1) + go func() { + defer o.wg.Done() + ticker := time.NewTicker(recoverySweepInterval) + defer ticker.Stop() + for { + select { + case <-o.sweeperCtx.Done(): + return + case <-ticker.C: + // Bound each sweep so a slow DB can't wedge the goroutine, but derive it + // from sweeperCtx (do NOT detach) so cancelling sweeperCtx at Shutdown + // interrupts a sweep already blocked mid-statement rather than letting it + // run to its own timeout against a closing pool. + sctx, cancel := context.WithTimeout(o.sweeperCtx, jobFinalizeTimeout) + n, err := o.jobs.FailStuckJobs(sctx, "job did not complete before a service restart") + cancel() + if err != nil { + // A cancellation here is the expected outcome when Shutdown interrupts + // an in-flight sweep, not a real failure — don't log it as an error. + if o.sweeperCtx.Err() == nil { + slog.ErrorContext(o.sweeperCtx, "periodic stuck-job sweep failed", "error", err) + } + } else if n > 0 { + slog.InfoContext(o.sweeperCtx, "periodic stuck-job sweep recovered jobs", "count", n) + } + } + } + }() +} + +// Shutdown drains in-flight dispatch runs so a graceful shutdown doesn't close +// the database pool out from under a dispatch that already created an upstream +// campaign but hasn't persisted it yet. After Shutdown is called, Start rejects +// new work. +// +// The two phases have SEPARATE budgets so neither starves the other: +// - Clean drain waits up to drainTimeout for all runs to finish on their own. +// - If that elapses, in-flight runs are cancelled (via the shared root +// context) and Shutdown then waits a post-cancel grace for them to observe +// cancellation and finalize before returning (and the caller closes the +// pool). That grace is bounded by CancelGracePeriod AND by whatever the +// outer ctx still allows, whichever is sooner — so the grace timer can +// never push total shutdown past the budget the caller reserved. +// +// ctx is the overall budget for BOTH phases (drain + grace); the caller sizes it +// as dispatchDrainTimeout + CancelGracePeriod. Passing ctx already limited to +// only drainTimeout would leave no room for the grace and defeat its purpose. +func (o *Orchestrator) Shutdown(ctx context.Context, drainTimeout time.Duration) error { + o.mu.Lock() + o.draining = true + o.mu.Unlock() + + // Cancel the periodic recovery sweeper FIRST, before the dispatch drain. It's + // maintenance, not in-flight work, so it stops immediately; cancelling its + // dedicated context also interrupts any sweep currently blocked in the DB + // (see StartRecoverySweeper). Doing this up front means the sweeper's own + // shutdown never draws down the dispatch-drain budget, and wg.Wait below then + // blocks only on real dispatch runs. Safe whether or not the sweeper started. + o.sweeperOnce.Do(o.sweeperCancel) + + done := make(chan struct{}) + go func() { + o.wg.Wait() + close(done) + }() + + // Phase 1: clean drain, bounded by drainTimeout and the outer ctx. + drainCtx, cancelDrain := context.WithTimeout(ctx, drainTimeout) + defer cancelDrain() + select { + case <-done: + o.rootCancel() + return nil + case <-drainCtx.Done(): + if ctx.Err() != nil { + // The OUTER budget (not just the drain window) is exhausted: cancel and + // return without a grace wait we have no budget for. + o.rootCancel() + return ctx.Err() + } + } + + // Phase 2: drain deadline hit but outer budget remains. Cancel in-flight runs + // and give them a post-cancel grace to unwind before the pool is closed. + o.rootCancel() + graceDur := CancelGracePeriod + if deadline, ok := ctx.Deadline(); ok { + if remaining := time.Until(deadline); remaining < graceDur { + graceDur = remaining + } + } + if graceDur <= 0 { + return context.DeadlineExceeded + } + grace := time.NewTimer(graceDur) + defer grace.Stop() + select { + case <-done: + return nil + case <-grace.C: + return context.DeadlineExceeded + case <-ctx.Done(): + // Caller cancelled (not just a deadline we already accounted for): stop + // waiting immediately rather than blocking out the whole grace window. + return ctx.Err() + } +} + +// platformResult is the per-platform outcome recorded in the job result. +// +// Skipped marks the single-flight SKIP case: another concurrent dispatch owns +// this (brief, platform) pair, so THIS request did not create the campaign and +// its outcome is owned by that other dispatch. A skip is neither a success nor a +// failure for this job — recording it as a failure would falsely make the +// aggregate job terminal-failed/partial even when the owning dispatch succeeds +// (and GetJob only decodes the stored result; it never re-checks the campaign +// row). aggregateStatus therefore excludes skipped platforms from the failure +// tally, and a job with only skipped platforms terminalizes as succeeded (nothing +// revisits a running job — GetJob only reads it — so leaving it running would +// strand it until the staleness sweeper failed it, turning a correct deferral into +// a spurious failure). Full cross-request reconciliation (this job actively +// adopting the owner's result) is tracked under LFXV2-2665. +type platformResult struct { + Platform string `json:"platform"` + OK bool `json:"ok"` + Skipped bool `json:"skipped,omitempty"` + CampaignID string `json:"campaign_id,omitempty"` + Error string `json:"error,omitempty"` +} + +// Start creates a queued job for the brief and launches dispatch asynchronously, +// returning the job id immediately. The caller polls GetJob for progress. +// +// approvedVersion is the brief version the caller observed as 'approved'. Job +// creation is gated on the brief still being approved at that exact version +// (CreateJobForApprovedBrief), closing the approve→dispatch TOCTOU race: a +// concurrent ReplaceBrief (resets to draft) or ArchiveBrief committing between the +// caller's approval read and job creation bumps the brief's version; the guard +// (a SELECT ... FOR UPDATE re-check inside the job-insert transaction) then fails +// and Start returns domain.ErrStaleApproval (mapped to 409) rather than launching +// paid campaigns from a stale "approved" snapshot. +// +// The dispatch goroutine runs under the orchestrator's root context (not the +// request context), so it survives the request ending but can still be cancelled +// by Shutdown when the drain deadline expires. +func (o *Orchestrator) Start(ctx context.Context, brief *model.CampaignBrief, approvedVersion int64, platforms []model.Provider, config json.RawMessage) (string, error) { + // Register the run with the drain WaitGroup under the lock so a concurrent + // Shutdown can't start waiting between the draining check and wg.Add (which + // would let an untracked goroutine outlive Shutdown). + o.mu.Lock() + if o.draining { + o.mu.Unlock() + return "", &briefs.ConnServiceUnavailableError{Code: "503", Message: "service is shutting down; try again"} + } + o.wg.Add(1) + o.mu.Unlock() + + // Create the job ONLY if the brief is still approved at the version the caller + // read. This re-verifies approval atomically with job creation, so a concurrent + // replace/archive that commits in the approve→dispatch window causes this + // request to FAIL (ErrConflict → 409) instead of dispatching from stale state. + job, err := o.jobs.CreateJobForApprovedBrief(ctx, brief.ID, approvedVersion) + if err != nil { + o.wg.Done() + return "", err + } + + // Defensively copy the caller-owned slices before handing them to the async + // goroutine, so a caller that reuses/mutates its platforms slice or config + // bytes after Start returns can't race the dispatch run. + platformsCopy := append([]model.Provider(nil), platforms...) + configCopy := append(json.RawMessage(nil), config...) + + // Parent the run on the orchestrator's root context (not the request context), + // so it survives the request ending but can still be cancelled by Shutdown if + // the drain deadline expires. + dispatchCtx := o.rootCtx + go func() { + defer o.wg.Done() + o.run(dispatchCtx, job.ID, brief, platformsCopy, configCopy) + }() + + return job.ID, nil +} + +// run performs the parallel per-platform dispatch and finalizes the job. +func (o *Orchestrator) run(ctx context.Context, jobID string, brief *model.CampaignBrief, platforms []model.Provider, config json.RawMessage) { + // Mark the job running. Don't abort dispatch on failure (the work should still + // proceed and the final status write will correct it), but log it — silently + // dropping this can leave a job stuck at "queued" in the client's view. + if err := o.jobs.UpdateJobStatus(ctx, jobID, model.JobRunning, nil, ""); err != nil { + slog.ErrorContext(ctx, "failed to mark campaign job running", "job_id", jobID, "error", err) + } + + results := make([]platformResult, len(platforms)) + g, gctx := errgroup.WithContext(ctx) + + for i, p := range platforms { + i, p := i, p + g.Go(func() (rerr error) { + // A single platform failure must not cancel the others, so we never + // return a non-nil error from the group; failures are recorded. + res := platformResult{Platform: string(p)} + + // Bound concurrent dispatches process-wide (across all jobs) via the + // shared semaphore. Honor cancellation so a draining shutdown doesn't + // block here, and bound the wait so a large backlog can't keep a job + // queued so long it looks stuck to the recovery sweep (which would then + // wrongly fail a still-live job). If no slot frees in time, record this + // platform as failed and let the job finalize promptly. + queueCtx, cancelQueue := context.WithTimeout(gctx, dispatchQueueTimeout) + select { + case o.sem <- struct{}{}: + cancelQueue() + defer func() { <-o.sem }() + case <-queueCtx.Done(): + cancelQueue() + if gctx.Err() != nil { + res.Error = "dispatch cancelled" + } else { + res.Error = "dispatch queue timed out waiting for a slot" + } + results[i] = res + return nil + } + + // Recover from a panic in a dispatcher (or future code here): a panic + // in this detached goroutine would otherwise crash the whole process + // mid-job. Record it as a platform failure and keep the group intact. + defer func() { + if r := recover(); r != nil { + slog.ErrorContext(gctx, "panic during platform dispatch", "platform", p, "job_id", jobID, "panic", r) + res.OK = false + res.Error = "internal error during dispatch" + results[i] = res + rerr = nil + } + }() + + results[i] = o.dispatchPlatform(gctx, jobID, brief, p, config) + return nil + }) + } + // Wait for all dispatches to finish. Each goroutine returns nil and records + // per-platform failures in results, so g.Wait() is expected to be nil; it is + // checked only as a defensive guard in case a future change starts returning + // an error from a Go func (errgroup.Wait returns the first such error). + if werr := g.Wait(); werr != nil { + slog.ErrorContext(ctx, "campaign dispatch returned an error", "job_id", jobID, "error", werr) + } + + // Finalize on a context detached from cancellation: on the drain-timeout path + // Shutdown cancels this run's ctx, and using it for the terminal write would + // guarantee the write fails and leave the job stuck non-terminal. A bounded + // detached context lets the job always reach a terminal state. + finCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), jobFinalizeTimeout) + defer cancel() + + status := aggregateStatus(results) + payload, err := json.Marshal(results) + if err != nil { + // Don't store a null result (which would make the job unpollable); + // record the marshal failure in the job's error field and fail the job. + slog.ErrorContext(finCtx, "failed to marshal job result", "job_id", jobID, "error", err) + if uerr := o.jobs.UpdateJobStatus(finCtx, jobID, model.JobFailed, nil, "failed to serialize job result: "+err.Error()); uerr != nil { + slog.ErrorContext(finCtx, "failed to finalize campaign job", "job_id", jobID, "error", uerr) + } + return + } + if err := o.jobs.UpdateJobStatus(finCtx, jobID, status, payload, ""); err != nil { + slog.ErrorContext(finCtx, "failed to finalize campaign job", "job_id", jobID, "error", err) + } +} + +// dispatchPlatform creates (or reuses) the campaign for a single platform. +// Single-flight is enforced by an atomic claim row (ClaimCampaignDispatch: +// INSERT ... ON CONFLICT (brief_id, platform) DO NOTHING) — no held connection, +// no blocking lock — so two concurrent create-campaigns for the same pair cannot +// both create an upstream campaign: exactly one wins the claim (the unique index +// arbitrates); the other reuses the existing row or, if it's still pending, is +// reported in-progress. campaign_id is always the upstream platform id, so the +// field means the same on the reuse and create paths. +func (o *Orchestrator) dispatchPlatform(ctx context.Context, jobID string, brief *model.CampaignBrief, p model.Provider, config json.RawMessage) platformResult { + res := platformResult{Platform: string(p)} + + // Fast path: if this pair already has a completed campaign (upstream id set), + // reuse it — idempotent, and valid even if no dispatcher is registered for the + // platform anymore. A real DB error here (anything other than "no such row") must + // NOT be swallowed as "no existing campaign": proceeding to claim/dispatch when an + // existing campaign simply couldn't be loaded risks a duplicate upstream create. + // Only ErrNotFound is a clean "nothing yet, proceed". + existing, lerr := o.campaigns.GetCampaignByPlatform(ctx, brief.ProjectID, brief.ID, p) + switch { + case lerr == nil && existing.PlatformCampaignID != "": + res.OK = true + res.CampaignID = existing.PlatformCampaignID + return res + case lerr == nil: + // A row exists but has no upstream id yet (a prior pending/failed attempt) — + // fall through to the claim path, which reconciles it. + case errors.Is(lerr, domain.ErrNotFound): + // No campaign for this pair yet — fall through to claim/dispatch. + default: + // A transient/real DB failure — surface it as a platform failure rather than + // dispatching blind (which could duplicate an existing-but-unloaded campaign). + slog.ErrorContext(ctx, "idempotency lookup failed", "platform", p, "job_id", jobID, "error", lerr) + res.Error = "could not check for an existing campaign" + return res + } + + // Resolve the dispatcher BEFORE claiming: a "no dispatcher" outcome must not + // leave a permanent pending claim that blocks the pair forever (which, with + // the currently-empty dispatcher map, would happen on every request). + d, ok := o.dispatchers[p] + if !ok { + res.Error = "no dispatcher registered for platform" + return res + } + + // Single-flight claim: atomically insert a 'pending' placeholder for (brief, + // platform). Exactly one worker across all replicas wins (the unique index + // arbitrates) — no held connection, no blocking lock. + claimed, existing, err := o.campaigns.ClaimCampaignDispatch(ctx, brief.ProjectID, brief.ID, p, jobID) + if err != nil { + slog.ErrorContext(ctx, "claim dispatch failed", "platform", p, "job_id", jobID, "error", err) + res.Error = "could not claim campaign dispatch" + return res + } + if !claimed { + // Another worker owns (or already completed) this pair. + if existing != nil && existing.PlatformCampaignID != "" { + // Already created upstream — reuse it (idempotent). + res.OK = true + res.CampaignID = existing.PlatformCampaignID + return res + } + // Another worker holds the pending claim: don't dispatch again (the point of + // the claim). This job did not create the campaign — its outcome belongs to + // the owning dispatch — so record it as SKIPPED, not failed. Recording a skip + // as a failure would falsely drive THIS job to terminal failed/partial even + // when the owner succeeds (GetJob only decodes the stored result and never + // re-checks the campaign row, so the false failure would be permanent). + // aggregateStatus excludes skipped platforms from the failure tally and + // returns succeeded for a wholly-skipped job (leaving it running would strand + // it until the staleness sweeper failed it, since nothing revisits a running + // job). Fully adopting the owner's async result into this job is tracked under + // LFXV2-2665. + res.Skipped = true + res.Error = "skipped: another concurrent dispatch owns this platform" + return res + } + + // We own the claim (a 'pending' row now exists). If we fail BEFORE the + // upstream campaign is created, release the pending claim so the pair isn't + // blocked and can be retried. Once the upstream campaign exists, we do NOT + // release (the row is the record of the created campaign / recoverable orphan). + releaseClaim := func() { + // Use a fresh bounded context, not the dispatch ctx: on shutdown/timeout the + // dispatch ctx is already cancelled, and reusing it would make the cleanup + // DELETE fail and leak the pending claim exactly when we most need to free it. + rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), claimReleaseTimeout) + defer cancel() + if derr := o.campaigns.DeleteDispatchClaim(rctx, brief.ID, p); derr != nil { + slog.ErrorContext(rctx, "failed to release pending dispatch claim", "platform", p, "job_id", jobID, "error", derr) + } + } + + // Bound the provider call so a hung upstream can't hold this job "running" and + // its semaphore slot indefinitely. Derived from the dispatch ctx so a shutdown + // cancel still propagates, but with its own ceiling. + callCtx, cancelCall := context.WithTimeout(ctx, providerCallTimeout) + defer cancelCall() + campaign, derr := d.Dispatch(callCtx, brief, p, config) + if derr != nil { + // A dispatch error usually does NOT prove the provider rejected the create — + // a timeout or dropped connection can leave a campaign created upstream — so + // by default we RETAIN the pending claim to block a blind retry from + // double-creating. The exception: a dispatcher can signal (via + // NoUpstreamCreate) that the error occurred before any create call (e.g. + // input/config validation), in which case releasing the claim to allow a + // retry is safe. + if dispatchErrIsPreCreate(derr) { + slog.ErrorContext(ctx, "platform dispatch failed before upstream create (claim released)", "platform", p, "job_id", jobID, "error", derr) + releaseClaim() + } else { + // The claim is RETAINED (outcome unknown, blind retry could double-create). + // The platform clients' partial-result contract lets Dispatch return a + // non-nil campaign carrying the created upstream id ALONGSIDE the error + // (the campaign POST succeeded but a later step failed). If it did, stamp + // that upstream id onto the retained pending row so the orphaned upstream + // campaign is RECONCILABLE later instead of leaving an anonymous claim. + // Persist on a context DETACHED from the dispatch ctx (mirroring the + // success path) so a shutdown-cancelled dispatch ctx can't drop the record + // of a paid campaign that actually exists. + if campaign != nil && campaign.PlatformCampaignID != "" { + campaign.JobID = &jobID + campaign.BriefID = brief.ID + campaign.ProjectID = brief.ProjectID + campaign.Platform = p + // Keep the row 'pending' so it stays a recoverable orphan (not a + // completed campaign): the dispatch did not succeed, only the upstream + // create partially happened. + campaign.Status = "pending" + persistCtx, cancelPersist := context.WithTimeout(context.WithoutCancel(ctx), persistResultTimeout) + if _, perr := o.campaigns.UpsertCampaign(persistCtx, campaign); perr != nil { + slog.ErrorContext(ctx, "failed to record partial upstream campaign id on retained pending claim", + "platform", p, "job_id", jobID, "platform_campaign_id", campaign.PlatformCampaignID, "error", perr) + } else { + slog.ErrorContext(ctx, "platform dispatch failed after upstream create; recorded orphaned upstream id on retained pending claim", + "platform", p, "job_id", jobID, "platform_campaign_id", campaign.PlatformCampaignID, "error", derr) + } + cancelPersist() + } else { + slog.ErrorContext(ctx, "platform dispatch failed (claim retained; outcome unknown)", "platform", p, "job_id", jobID, "error", derr) + } + } + res.Error = "platform campaign creation failed" + return res + } + if campaign == nil { + // A (nil, nil) result is ambiguous: it does NOT prove no upstream campaign + // was created (a dispatcher could create the campaign, then fail to build + // its return value). Treat it like the ambiguous error path — RETAIN the + // claim so a blind retry can't double-create; the pending row flags the + // pair for reconciliation. + slog.ErrorContext(ctx, "dispatcher returned no campaign (claim retained; outcome unknown)", "platform", p, "job_id", jobID) + res.Error = "dispatcher returned no campaign" + return res + } + if campaign.PlatformCampaignID == "" { + // An empty upstream id is likewise ambiguous (the create may have happened + // but the id wasn't captured), so RETAIN the claim rather than releasing it + // and risking a duplicate on retry. + slog.ErrorContext(ctx, "dispatcher returned no upstream campaign id (claim retained; outcome unknown)", "platform", p, "job_id", jobID) + res.Error = "dispatcher returned no upstream campaign id" + return res + } + // Stamp ownership, then update the claimed row in place (Upsert on the same + // (brief, platform) fills in the real upstream id and status). + campaign.JobID = &jobID + campaign.BriefID = brief.ID + campaign.ProjectID = brief.ProjectID + campaign.Platform = p + // Persist the successful result on a context DETACHED from the dispatch ctx. + // The upstream (paid) campaign now EXISTS; on the phase-two shutdown path + // rootCancel has already cancelled the dispatch ctx, and reusing it here would + // make pgx reject the upsert immediately — losing the record of a campaign that + // was actually created (an unreconcilable orphan) even though Shutdown is still + // inside its grace window. A bounded detached context (persistResultTimeout, + // sized to fit within CancelGracePeriod) lets the persist complete during grace + // while still bounding it so it can never hang shutdown. + persistCtx, cancelPersist := context.WithTimeout(context.WithoutCancel(ctx), persistResultTimeout) + defer cancelPersist() + if _, err := o.campaigns.UpsertCampaign(persistCtx, campaign); err != nil { + // The upstream (paid) campaign was created but recording it failed. The + // 'pending' claim row remains, so this is recoverable/reconcilable out of + // band and a duplicate can't be created behind the claim. Log the raw error + // and the orphaned upstream id; keep the id in the result. + slog.ErrorContext(ctx, "persist campaign failed after upstream create — pending claim retained", + "platform", p, "job_id", jobID, "platform_campaign_id", campaign.PlatformCampaignID, "error", err) + res.Error = "created upstream campaign but failed to record it; see logs" + res.CampaignID = campaign.PlatformCampaignID + return res + } + res.OK = true + res.CampaignID = campaign.PlatformCampaignID + return res +} + +// aggregateStatus folds per-platform outcomes into the job's status. +// +// Skipped platforms (single-flight SKIP: another dispatch owns the pair) are NOT +// counted as failures — a skip is a deferral to that owner, not this job's outcome +// and not this job's work to finish. If a platform was skipped and none of THIS +// job's own dispatches failed, the job terminalizes as SUCCEEDED: it is neither a +// failure (spurious) nor left non-terminal (a stuck `running` job is never +// revisited — GetJob only reads it and the recovery sweeper would eventually fail +// it after staleJobCutoff). The per-platform result carries Skipped=true so a +// caller can see which platforms a concurrent request handled; full cross-request +// adoption of the owner's result is tracked under LFXV2-2665. +func aggregateStatus(results []platformResult) model.JobStatus { + var ok, failed, skipped int + for _, r := range results { + switch { + case r.Skipped: + skipped++ + case r.OK: + ok++ + default: + failed++ + } + } + switch { + case failed > 0 && ok == 0 && skipped == 0: + // Every platform this job actually dispatched failed. + return model.JobFailed + case failed > 0: + // A real mix of success and failure (any skips remain pending but at least + // one platform definitively failed — surface that as partial rather than + // hiding it behind a still-running status). + return model.JobPartial + case skipped > 0: + // No failures, and every platform this job actually dispatched succeeded; the + // remaining pair(s) were SKIPPED because a concurrent dispatch already owns + // the (brief, platform) claim and is creating them. A skip is a deferral to + // that owner, NOT this job's failure and NOT this job's work to finish — the + // owner's own job records the real per-platform result. So this job is + // TERMINAL: return succeeded rather than staying `running` forever (nothing + // revisits a running job — GetJob only reads it and the recovery sweeper would + // eventually fail it after staleJobCutoff, turning a correct deferral into a + // spurious failure). The per-platform result already carries Skipped=true so a + // caller can see which platforms were handled by a concurrent request; full + // cross-request adoption of the owner's outcome is tracked under LFXV2-2665. + return model.JobSucceeded + default: + return model.JobSucceeded + } +} diff --git a/internal/service/orchestrator_test.go b/internal/service/orchestrator_test.go new file mode 100644 index 00000000..302fdb9d --- /dev/null +++ b/internal/service/orchestrator_test.go @@ -0,0 +1,1231 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package service + +import ( + "context" + "encoding/json" + "errors" + "strings" + "sync" + "testing" + "time" + + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-campaign-service/internal/domain/model" +) + +// fakeJobRepo records job status transitions. +type fakeJobRepo struct { + mu sync.Mutex + jobs map[string]*model.CampaignJob + counter int + failStuckCalls int +} + +func newFakeJobRepo() *fakeJobRepo { return &fakeJobRepo{jobs: map[string]*model.CampaignJob{}} } + +func (r *fakeJobRepo) CreateJob(_ context.Context, briefID string) (*model.CampaignJob, error) { + r.mu.Lock() + defer r.mu.Unlock() + return r.createLocked(briefID) +} + +// createLocked inserts a queued job. Caller must hold r.mu. +func (r *fakeJobRepo) createLocked(briefID string) (*model.CampaignJob, error) { + r.counter++ + id := "job-" + string(rune('a'+r.counter)) + j := &model.CampaignJob{ID: id, BriefID: briefID, Status: model.JobQueued} + r.jobs[id] = j + return j, nil +} + +// CreateJobForApprovedBrief mirrors the unconditional create for the orchestrator +// tests (which don't wire a brief store, so the approval guard is exercised +// separately by the brief-service TOCTOU test with its own version-aware fake). +func (r *fakeJobRepo) CreateJobForApprovedBrief(_ context.Context, briefID string, _ int64) (*model.CampaignJob, error) { + r.mu.Lock() + defer r.mu.Unlock() + return r.createLocked(briefID) +} + +func (r *fakeJobRepo) GetJob(_ context.Context, _, id string) (*model.CampaignJob, error) { + r.mu.Lock() + defer r.mu.Unlock() + j, ok := r.jobs[id] + if !ok { + return nil, errors.New("not found") + } + // Return a snapshot so callers don't race with concurrent UpdateJobStatus. + cp := *j + return &cp, nil +} + +func (r *fakeJobRepo) UpdateJobStatus(_ context.Context, id string, status model.JobStatus, result []byte, jobErr string) error { + r.mu.Lock() + defer r.mu.Unlock() + j := r.jobs[id] + j.Status = status + j.Result = result + j.Error = jobErr + return nil +} + +func (r *fakeJobRepo) failStuckCallCount() int { + r.mu.Lock() + defer r.mu.Unlock() + return r.failStuckCalls +} + +func (r *fakeJobRepo) FailStuckJobs(_ context.Context, jobErr string) (int64, error) { + r.mu.Lock() + defer r.mu.Unlock() + r.failStuckCalls++ + var n int64 + for _, j := range r.jobs { + if j.Status == model.JobQueued || j.Status == model.JobRunning { + j.Status = model.JobFailed + j.Error = jobErr + n++ + } + } + return n, nil +} + +// fakeCampaignRepo records upserted campaigns and simulates the claim table. +type fakeCampaignRepo struct { + mu sync.Mutex + upserted []*model.Campaign + // existing maps briefID+"|"+platform to a pre-existing campaign, letting a + // test simulate a brief already dispatched to a platform (idempotency guard). + existing map[string]*model.Campaign + // byPlatformErr, when set, is returned by GetCampaignByPlatform to simulate a + // transient lookup failure. + byPlatformErr error + // claimErr, when set, is returned by ClaimCampaignDispatch. + claimErr error +} + +func (r *fakeCampaignRepo) GetCampaign(context.Context, string, string, string) (*model.Campaign, error) { + return nil, errors.New("unused") +} + +func (r *fakeCampaignRepo) GetCampaignByPlatform(_ context.Context, _ string, briefID string, platform model.Provider) (*model.Campaign, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.byPlatformErr != nil { + return nil, r.byPlatformErr + } + if c, ok := r.existing[briefID+"|"+string(platform)]; ok { + return c, nil + } + return nil, domain.ErrNotFound +} + +// ClaimCampaignDispatch simulates INSERT ... ON CONFLICT DO NOTHING: if an entry +// for (brief, platform) already exists it's a conflict (not claimed) returning +// the existing row; otherwise it inserts a pending placeholder and claims. +func (r *fakeCampaignRepo) ClaimCampaignDispatch(_ context.Context, projectID, briefID string, platform model.Provider, jobID string) (bool, *model.Campaign, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.claimErr != nil { + return false, nil, r.claimErr + } + key := briefID + "|" + string(platform) + if c, ok := r.existing[key]; ok { + return false, c, nil + } + pending := &model.Campaign{ProjectID: projectID, BriefID: briefID, Platform: platform, JobID: &jobID, Status: "pending"} + if r.existing == nil { + r.existing = map[string]*model.Campaign{} + } + r.existing[key] = pending + return true, pending, nil +} + +func (r *fakeCampaignRepo) DeleteDispatchClaim(_ context.Context, briefID string, platform model.Provider) error { + r.mu.Lock() + defer r.mu.Unlock() + key := briefID + "|" + string(platform) + if c, ok := r.existing[key]; ok && c.Status == "pending" { + delete(r.existing, key) + } + return nil +} + +func (r *fakeCampaignRepo) UpsertCampaign(_ context.Context, c *model.Campaign) (*model.Campaign, error) { + r.mu.Lock() + defer r.mu.Unlock() + c.Version = 1 + r.upserted = append(r.upserted, c) + // Mirror the real ON CONFLICT (brief_id, platform) DO UPDATE: the (brief, + // platform) row is updated in place, so a subsequent lookup sees the new + // platform_campaign_id/status. + if r.existing == nil { + r.existing = map[string]*model.Campaign{} + } + r.existing[c.BriefID+"|"+string(c.Platform)] = c + return c, nil +} +func (r *fakeCampaignRepo) ReplaceCampaign(context.Context, *model.Campaign, int64) (*model.Campaign, error) { + return nil, errors.New("unused") +} + +// okDispatcher always succeeds. +type okDispatcher struct{} + +func (okDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return &model.Campaign{PlatformCampaignID: "pc-" + string(p), Status: "active", CampaignName: "n"}, nil +} + +// failDispatcher always fails. +type failDispatcher struct{} + +func (failDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, _ model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return nil, errors.New("boom") +} + +// nilDispatcher returns (nil, nil) — a misbehaving dispatcher that must be +// handled as a failure rather than panicking on the ownership stamp. +type nilDispatcher struct{} + +func (nilDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, _ model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return nil, nil //nolint:nilnil // deliberately exercises the nil-campaign guard +} + +func waitForTerminal(t *testing.T, jobs *fakeJobRepo, id string) *model.CampaignJob { + t.Helper() + for i := 0; i < 100; i++ { + j, _ := jobs.GetJob(context.Background(), "", id) + if j.Status.Terminal() { + return j + } + time.Sleep(5 * time.Millisecond) + } + t.Fatal("job did not reach terminal status") + return nil +} + +// waitForFinalized waits until the run's finalize write has landed (a non-empty +// result recorded), regardless of whether the resulting status is terminal. A +// job whose only outcomes are single-flight SKIPs finalizes to a non-terminal +// 'running' status (its skipped pairs are owned by another dispatch), so such a +// job never satisfies waitForTerminal — this helper observes its finalize instead. +func waitForFinalized(t *testing.T, jobs *fakeJobRepo, id string) *model.CampaignJob { + t.Helper() + for i := 0; i < 100; i++ { + j, _ := jobs.GetJob(context.Background(), "", id) + if len(j.Result) > 0 { + return j + } + time.Sleep(5 * time.Millisecond) + } + t.Fatal("job result was never finalized") + return nil +} + +func TestOrchestrator_AllSucceed(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + model.ProviderLinkedInAds: okDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, err := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds, model.ProviderLinkedInAds}, nil) + if err != nil { + t.Fatalf("Start: %v", err) + } + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded", j.Status) + } + if len(camps.upserted) != 2 { + t.Errorf("upserted %d campaigns, want 2", len(camps.upserted)) + } +} + +func TestOrchestrator_PartialFailure(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + model.ProviderLinkedInAds: failDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds, model.ProviderLinkedInAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobPartial { + t.Errorf("status = %s, want partial", j.Status) + } +} + +func TestOrchestrator_NoDispatcherFails(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, nil) // no dispatchers + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } +} + +func TestOrchestrator_NilCampaignFailsWithoutPanic(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: nilDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + if len(camps.upserted) != 0 { + t.Errorf("upserted %d campaigns, want 0 (nil campaign must not persist)", len(camps.upserted)) + } +} + +// countingDispatcher records how many times Dispatch is called, to prove the +// idempotency guard skips the upstream create. +type countingDispatcher struct { + mu sync.Mutex + calls int +} + +func (d *countingDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + d.mu.Lock() + d.calls++ + d.mu.Unlock() + return &model.Campaign{PlatformCampaignID: "pc-" + string(p), Status: "active", CampaignName: "n"}, nil +} + +// TestOrchestrator_SkipsAlreadyDispatchedPlatform verifies that a brief already +// carrying a campaign with an upstream id for a platform does NOT re-invoke the +// platform's create API (which would spend money on a duplicate). +func TestOrchestrator_SkipsAlreadyDispatchedPlatform(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{existing: map[string]*model.Campaign{ + "b1|" + string(model.ProviderGoogleAds): {ID: "existing-c1", PlatformCampaignID: "pc-google-ads"}, + }} + disp := &countingDispatcher{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, err := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + if err != nil { + t.Fatalf("Start: %v", err) + } + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded", j.Status) + } + disp.mu.Lock() + calls := disp.calls + disp.mu.Unlock() + if calls != 0 { + t.Errorf("Dispatch called %d times, want 0 (existing campaign must be reused)", calls) + } + if len(camps.upserted) != 0 { + t.Errorf("upserted %d campaigns, want 0 (no re-create)", len(camps.upserted)) + } + // The reuse path must report the upstream platform campaign id (not the DB + // row id), so campaign_id means the same thing as on the create path. + if !strings.Contains(string(j.Result), "pc-google-ads") { + t.Errorf("result = %s, want it to carry the upstream campaign id pc-google-ads", j.Result) + } + if strings.Contains(string(j.Result), "existing-c1") { + t.Errorf("result = %s, must not leak the DB row id existing-c1", j.Result) + } +} + +// TestOrchestrator_ClaimErrorIsFailure verifies that a failure to claim the +// dispatch slot is recorded as a platform failure and the dispatcher is never +// called (so no create can duplicate). +func TestOrchestrator_ClaimErrorIsFailure(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{claimErr: errors.New("db down")} + disp := &countingDispatcher{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + disp.mu.Lock() + calls := disp.calls + disp.mu.Unlock() + if calls != 0 { + t.Errorf("Dispatch called %d times, want 0 (must not create when the claim failed)", calls) + } +} + +// TestOrchestrator_IdempotencyLookupErrorIsFailure verifies that a REAL DB error +// from the idempotency lookup (GetCampaignByPlatform) — anything other than +// ErrNotFound — is surfaced as a platform failure and the dispatcher is never +// called. Otherwise a transient read failure would be treated like "no existing +// campaign" and dispatch could duplicate an existing-but-unloaded campaign. +func TestOrchestrator_IdempotencyLookupErrorIsFailure(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{byPlatformErr: errors.New("db connection reset")} + disp := &countingDispatcher{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + disp.mu.Lock() + calls := disp.calls + disp.mu.Unlock() + if calls != 0 { + t.Errorf("Dispatch called %d times, want 0 (a lookup error must not fall through to dispatch)", calls) + } + if len(camps.upserted) != 0 { + t.Errorf("upserted %d campaigns, want 0 (no create on a lookup error)", len(camps.upserted)) + } +} + +// TestOrchestrator_AlreadyClaimedPendingSkips verifies that when another worker +// holds the pending claim (no upstream id yet), this worker does not dispatch and +// the skip is NOT recorded as a terminal failure: a single skipped platform is a +// deferral to the owning dispatch, so the job terminalizes as succeeded (not +// failed, which would be spurious; not left running, which the staleness sweeper +// would later fail), rather than the old behavior of falsely failing it. +func TestOrchestrator_AlreadyClaimedPendingSkips(t *testing.T) { + jobs := newFakeJobRepo() + // Seed a pending claim (no upstream id) for the pair, so ClaimCampaignDispatch + // returns not-claimed with a still-pending row. + camps := &fakeCampaignRepo{existing: map[string]*model.Campaign{ + "b1|" + string(model.ProviderGoogleAds): {ID: "c1", Status: "pending", PlatformCampaignID: ""}, + }} + disp := &countingDispatcher{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + // A skipped platform (owned by a concurrent dispatch) is a deferral, not a + // failure — the job terminalizes as SUCCEEDED (not stuck-running, which the + // recovery sweeper would later fail; not failed, which would be spurious). + j := waitForFinalized(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded (a lone skipped platform is a deferral, terminalizes succeeded)", j.Status) + } + disp.mu.Lock() + calls := disp.calls + disp.mu.Unlock() + if calls != 0 { + t.Errorf("Dispatch called %d times, want 0 (another worker holds the claim)", calls) + } + if !strings.Contains(string(j.Result), "another concurrent dispatch owns") { + t.Errorf("result = %s, want a concurrent-owner skip message", j.Result) + } + if !strings.Contains(string(j.Result), "\"skipped\":true") { + t.Errorf("result = %s, want the platform marked skipped:true", j.Result) + } +} + +// TestClaimCampaignDispatch_ConcurrentSingleWinner exercises the ACTUAL race the +// single-flight claim guards against: N goroutines calling ClaimCampaignDispatch +// for the SAME (brief, platform) at the same time. Exactly one must win +// (claimed=true) and every loser must cleanly observe claimed=false with no error +// and the SAME pending row — the ON CONFLICT (brief_id, platform) DO NOTHING +// arbitration the design leans on. The prior claim tests only pre-seed a claimed +// row and call Start once, so they never run two claimers concurrently. +func TestClaimCampaignDispatch_ConcurrentSingleWinner(t *testing.T) { + // The fake repo models ON CONFLICT DO NOTHING under a mutex: first caller + // inserts + returns claimed=true; every later caller sees the existing row and + // returns claimed=false — the same arbitration Postgres provides. + repo := &fakeCampaignRepo{} + + const n = 32 + var ( + wg sync.WaitGroup + start = make(chan struct{}) + mu sync.Mutex + wins int + errs int + rowIDs = map[*model.Campaign]struct{}{} + ) + for i := 0; i < n; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + <-start // release all goroutines at once to maximize contention + claimed, row, err := repo.ClaimCampaignDispatch( + context.Background(), "cncf", "b1", model.ProviderGoogleAds, "job1") + mu.Lock() + defer mu.Unlock() + if err != nil { + errs++ + return + } + if claimed { + wins++ + } + if row != nil { + rowIDs[row] = struct{}{} + } + }(i) + } + close(start) + wg.Wait() + + if errs != 0 { + t.Errorf("got %d errors; every claimer (winner or loser) must return nil error", errs) + } + if wins != 1 { + t.Errorf("exactly one goroutine must win the claim, got %d winners", wins) + } + if len(rowIDs) != 1 { + t.Errorf("all claimers must observe the SAME pending row, got %d distinct rows", len(rowIDs)) + } +} + +// TestOrchestrator_SkipDoesNotFailAlongsideSuccess verifies that when one +// platform succeeds and another is skipped (owned by a concurrent dispatch), the +// job is not falsely reported failed/partial: with a real success and no real +// failure, an outstanding skip (a deferral to the owner) terminalizes the job as +// succeeded rather than a spurious failure/partial or a stuck running state. +func TestOrchestrator_SkipDoesNotFailAlongsideSuccess(t *testing.T) { + jobs := newFakeJobRepo() + // LinkedIn is already held pending by another dispatch; Google Ads is free. + camps := &fakeCampaignRepo{existing: map[string]*model.Campaign{ + "b1|" + string(model.ProviderLinkedInAds): {ID: "c1", Status: "pending", PlatformCampaignID: ""}, + }} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + model.ProviderLinkedInAds: okDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds, model.ProviderLinkedInAds}, nil) + j := waitForFinalized(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded (a skip alongside a success terminalizes succeeded, not failed/partial/stuck)", j.Status) + } +} + +func TestAggregateStatus(t *testing.T) { + cases := []struct { + name string + results []platformResult + want model.JobStatus + }{ + {"all ok", []platformResult{{OK: true}, {OK: true}}, model.JobSucceeded}, + {"all fail", []platformResult{{OK: false}, {OK: false}}, model.JobFailed}, + {"mixed", []platformResult{{OK: true}, {OK: false}}, model.JobPartial}, + // A single-flight SKIP is a deferral to the owning dispatch, not a failure + // and not this job's work to finish: it terminalizes as succeeded (not stuck + // running, which the sweeper would later fail). + {"only skipped", []platformResult{{Skipped: true}}, model.JobSucceeded}, + {"skip + ok", []platformResult{{OK: true}, {Skipped: true}}, model.JobSucceeded}, + // A real failure still surfaces even when another platform was skipped. + {"skip + fail", []platformResult{{OK: false}, {Skipped: true}}, model.JobPartial}, + {"ok + fail + skip", []platformResult{{OK: true}, {OK: false}, {Skipped: true}}, model.JobPartial}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := aggregateStatus(tc.results); got != tc.want { + t.Errorf("aggregateStatus = %s, want %s", got, tc.want) + } + }) + } +} + +// emptyIDDispatcher returns a non-nil campaign with no upstream id — a +// misbehaving dispatcher that must be recorded as a failure, not ok. +type emptyIDDispatcher struct{} + +func (emptyIDDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, _ model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return &model.Campaign{PlatformCampaignID: "", Status: "active", CampaignName: "n"}, nil +} + +// TestOrchestrator_EmptyUpstreamIDIsFailure verifies a dispatched campaign with +// no PlatformCampaignID is reported as a failure (not ok) and not persisted. +func TestOrchestrator_EmptyUpstreamIDIsFailure(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: emptyIDDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + if len(camps.upserted) != 0 { + t.Errorf("upserted %d, want 0 (empty upstream id must not persist)", len(camps.upserted)) + } +} + +// TestOrchestrator_ReusesExistingWhenDispatcherGone verifies the idempotency +// guard runs before dispatcher resolution: an already-persisted platform is +// reported ok on retry even if its dispatcher is no longer registered. +func TestOrchestrator_ReusesExistingWhenDispatcherGone(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{existing: map[string]*model.Campaign{ + "b1|" + string(model.ProviderGoogleAds): {ID: "c1", PlatformCampaignID: "pc-google-ads"}, + }} + // No dispatchers registered at all. + orch := NewOrchestrator(camps, jobs, nil) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded (existing campaign reused despite no dispatcher)", j.Status) + } + if !strings.Contains(string(j.Result), "pc-google-ads") { + t.Errorf("result = %s, want the reused upstream id", j.Result) + } +} + +// panicDispatcher panics — a misbehaving dispatcher that must not crash the +// process; the orchestrator must record it as a failure. +type panicDispatcher struct{} + +func (panicDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, _ model.Provider, _ json.RawMessage) (*model.Campaign, error) { + panic("boom in dispatcher") +} + +// TestOrchestrator_RecoversFromDispatcherPanic verifies a panicking dispatcher +// is recovered and recorded as a failure rather than crashing the goroutine. +func TestOrchestrator_RecoversFromDispatcherPanic(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: panicDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + // The panic value must not leak into the client-facing result. + if strings.Contains(string(j.Result), "boom in dispatcher") { + t.Errorf("result leaked the panic value: %s", j.Result) + } +} + +// persistErrCampaignRepo fails UpsertCampaign with a raw DB-like error. +type persistErrCampaignRepo struct{ fakeCampaignRepo } + +func (r *persistErrCampaignRepo) UpsertCampaign(context.Context, *model.Campaign) (*model.Campaign, error) { + return nil, errors.New("pq: duplicate key value violates unique constraint \"campaigns_pkey\"") +} + +// TestOrchestrator_PersistErrorIsSanitized verifies a raw persistence error is +// not surfaced verbatim in the client-facing job result. +func TestOrchestrator_PersistErrorIsSanitized(t *testing.T) { + jobs := newFakeJobRepo() + camps := &persistErrCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + if strings.Contains(string(j.Result), "pq:") || strings.Contains(string(j.Result), "constraint") { + t.Errorf("result leaked raw DB error: %s", j.Result) + } + // The message is sanitized but the upstream id is preserved so the orphaned + // campaign isn't lost. + if !strings.Contains(string(j.Result), "failed to record it") { + t.Errorf("result = %s, want the sanitized message", j.Result) + } + if !strings.Contains(string(j.Result), "pc-google-ads") { + t.Errorf("result = %s, want the upstream id preserved for reconciliation", j.Result) + } +} + +// claimCountingCampaignRepo records that each dispatch went through the claim. +type claimCountingCampaignRepo struct { + fakeCampaignRepo + cmu sync.Mutex + claims int +} + +func (r *claimCountingCampaignRepo) ClaimCampaignDispatch(ctx context.Context, projectID, briefID string, p model.Provider, jobID string) (bool, *model.Campaign, error) { + r.cmu.Lock() + r.claims++ + r.cmu.Unlock() + return r.fakeCampaignRepo.ClaimCampaignDispatch(ctx, projectID, briefID, p, jobID) +} + +// TestOrchestrator_DispatchGoesThroughClaim verifies each per-platform dispatch +// claims the (brief, platform) single-flight slot. +func TestOrchestrator_DispatchGoesThroughClaim(t *testing.T) { + jobs := newFakeJobRepo() + camps := &claimCountingCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + model.ProviderLinkedInAds: okDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds, model.ProviderLinkedInAds}, nil) + waitForTerminal(t, jobs, id) + camps.cmu.Lock() + defer camps.cmu.Unlock() + if camps.claims != 2 { + t.Errorf("ClaimCampaignDispatch called %d times, want 2 (one per platform)", camps.claims) + } +} + +// blockingDispatcher blocks until released, to test shutdown draining. +type blockingDispatcher struct { + started chan struct{} + release chan struct{} +} + +func (d *blockingDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + close(d.started) + <-d.release + return &model.Campaign{PlatformCampaignID: "pc-" + string(p), Status: "active", CampaignName: "n"}, nil +} + +// TestOrchestrator_ShutdownDrainsInFlight verifies Shutdown waits for an +// in-flight dispatch to finish before returning. +func TestOrchestrator_ShutdownDrainsInFlight(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + disp := &blockingDispatcher{started: make(chan struct{}), release: make(chan struct{})} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: disp, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + _, _ = orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-disp.started // dispatch is now in-flight + + shutdownReturned := make(chan error, 1) + go func() { + shutdownReturned <- orch.Shutdown(context.Background(), 5*time.Second) + }() + + // Shutdown must NOT return while the dispatch is blocked. + select { + case <-shutdownReturned: + t.Fatal("Shutdown returned before in-flight dispatch finished") + case <-time.After(50 * time.Millisecond): + } + + close(disp.release) // let dispatch complete + select { + case err := <-shutdownReturned: + if err != nil { + t.Errorf("Shutdown err = %v, want nil", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Shutdown did not return after dispatch completed") + } +} + +// TestOrchestrator_ShutdownGraceHonorsContextCancel verifies that during the +// post-cancel grace wait, a caller CANCEL of ctx (not just its deadline) ends +// the wait promptly instead of blocking the full CancelGracePeriod. +func TestOrchestrator_ShutdownGraceHonorsContextCancel(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + ctxSeen := make(chan context.Context, 1) + disp := &ctxCapturingDispatcher{started: make(chan struct{}), release: make(chan struct{}), ctxSeen: ctxSeen} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{model.ProviderGoogleAds: disp}) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + _, _ = orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-disp.started + <-ctxSeen + + // A cancelable ctx with no deadline; the dispatch never releases, so Shutdown + // enters the grace wait. Cancelling ctx must unblock it well before the full + // CancelGracePeriod. + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan error, 1) + go func() { done <- orch.Shutdown(ctx, 10*time.Millisecond) }() + + // Let the drain window elapse so Shutdown is in the grace wait, then cancel. + time.Sleep(40 * time.Millisecond) + start := time.Now() + cancel() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("Shutdown did not return after ctx cancel during grace") + } + if elapsed := time.Since(start); elapsed >= CancelGracePeriod { + t.Errorf("grace wait took %v after cancel; did not observe ctx cancellation", elapsed) + } + close(disp.release) +} + +// TestOrchestrator_RecoverySweeperStopsOnShutdown verifies the background +// recovery sweeper is tracked by the wait group and stops promptly on Shutdown +// (it must not block the drain until a ticker fires), so Shutdown returns +// quickly with no in-flight dispatch. +func TestOrchestrator_RecoverySweeperStopsOnShutdown(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, nil) + orch.StartRecoverySweeper() + + done := make(chan error, 1) + go func() { done <- orch.Shutdown(context.Background(), 5*time.Second) }() + + select { + case err := <-done: + if err != nil { + t.Errorf("Shutdown err = %v, want nil", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Shutdown did not return promptly; sweeper likely blocked the drain") + } + // The sweeper interval (5m) is far longer than this test, so FailStuckJobs + // must not have been called by a tick — it stopped on the stop signal. + if c := jobs.failStuckCallCount(); c != 0 { + t.Errorf("FailStuckJobs called %d times; sweeper should have stopped before any tick", c) + } +} + +// TestOrchestrator_StartRejectedAfterShutdown verifies Start refuses new work +// once Shutdown has been initiated. +func TestOrchestrator_StartRejectedAfterShutdown(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: okDispatcher{}, + }) + if err := orch.Shutdown(context.Background(), 5*time.Second); err != nil { + t.Fatalf("Shutdown: %v", err) + } + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + if _, err := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil); err == nil { + t.Fatal("expected Start to be rejected after Shutdown") + } +} + +// TestFailStuckJobs verifies the recovery scan fails only non-terminal jobs. +func TestFailStuckJobs(t *testing.T) { + jobs := newFakeJobRepo() + jobs.jobs["j-queued"] = &model.CampaignJob{ID: "j-queued", Status: model.JobQueued} + jobs.jobs["j-running"] = &model.CampaignJob{ID: "j-running", Status: model.JobRunning} + jobs.jobs["j-done"] = &model.CampaignJob{ID: "j-done", Status: model.JobSucceeded} + + n, err := jobs.FailStuckJobs(context.Background(), "restarted") + if err != nil { + t.Fatalf("FailStuckJobs: %v", err) + } + if n != 2 { + t.Errorf("failed %d jobs, want 2 (queued+running)", n) + } + if jobs.jobs["j-done"].Status != model.JobSucceeded { + t.Errorf("terminal job was altered: %s", jobs.jobs["j-done"].Status) + } + if jobs.jobs["j-queued"].Status != model.JobFailed || jobs.jobs["j-running"].Status != model.JobFailed { + t.Error("non-terminal jobs were not failed") + } +} + +// TestOrchestrator_ShutdownCancelsOnTimeout verifies that when the drain deadline +// expires, Shutdown cancels the in-flight run's context (rather than leaving it +// running against a closing pool). +func TestOrchestrator_ShutdownCancelsOnTimeout(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + ctxSeen := make(chan context.Context, 1) + disp := &ctxCapturingDispatcher{started: make(chan struct{}), release: make(chan struct{}), ctxSeen: ctxSeen} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{model.ProviderGoogleAds: disp}) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + _, _ = orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-disp.started + dctx := <-ctxSeen + + // Drain with an already-past deadline so Shutdown times out immediately and + // cancels the run context. + deadctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + _ = orch.Shutdown(deadctx, time.Millisecond) + + select { + case <-dctx.Done(): + // good: the dispatch context was cancelled by Shutdown's timeout path. + case <-time.After(time.Second): + t.Error("dispatch context was not cancelled after drain timeout") + } + close(disp.release) +} + +// TestOrchestrator_ShutdownGraceBoundedByContext verifies that when the drain +// deadline elapses while a dispatch is still stuck, the post-cancel grace wait +// does not exceed the caller's context budget (it must not add a full, +// wall-clock CancelGracePeriod on top of an already-expired deadline). +func TestOrchestrator_ShutdownGraceBoundedByContext(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + ctxSeen := make(chan context.Context, 1) + disp := &ctxCapturingDispatcher{started: make(chan struct{}), release: make(chan struct{}), ctxSeen: ctxSeen} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{model.ProviderGoogleAds: disp}) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + _, _ = orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-disp.started + <-ctxSeen // drain the captured ctx so Dispatch can proceed to <-release + + // Short drain budget; the dispatch never releases, so Shutdown must hit the + // deadline path and then wait at most the remaining budget for the grace, + // NOT the full CancelGracePeriod (which is >> this budget). + const budget = 50 * time.Millisecond + deadctx, cancel := context.WithTimeout(context.Background(), budget) + defer cancel() + + start := time.Now() + _ = orch.Shutdown(deadctx, time.Millisecond) + elapsed := time.Since(start) + + // Allow generous slack for scheduling, but it must be far below the full + // wall-clock CancelGracePeriod that the old (unbounded) timer would impose. + if elapsed >= CancelGracePeriod { + t.Errorf("Shutdown waited %v (>= full CancelGracePeriod %v); grace not bounded by context", elapsed, CancelGracePeriod) + } + close(disp.release) +} + +// TestOrchestrator_ShutdownGivesGraceWhenBudgetRemains verifies the two phases +// are budgeted separately: when the drain window elapses but the OUTER ctx still +// has budget, Shutdown actually spends a post-cancel grace waiting for the +// cancelled dispatch to unwind — it must NOT return immediately (which would let +// Container.Close close the pool mid-finalize). This guards the regression where +// Close passed a ctx limited to only the drain timeout, leaving zero grace. +func TestOrchestrator_ShutdownGivesGraceWhenBudgetRemains(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + ctxSeen := make(chan context.Context, 1) + disp := &ctxCapturingDispatcher{started: make(chan struct{}), release: make(chan struct{}), ctxSeen: ctxSeen} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{model.ProviderGoogleAds: disp}) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + _, _ = orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-disp.started + dctx := <-ctxSeen // the dispatch's own context, cancelled by rootCancel + + // Outer budget comfortably exceeds the tiny drain window, so after drain + // times out there is real grace budget left. + outerCtx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + graceObserved := make(chan struct{}) + go func() { + // The dispatch releases only once it observes its context cancellation — + // i.e. during the grace phase, proving grace actually ran. + <-dctx.Done() + close(graceObserved) + close(disp.release) + }() + + start := time.Now() + _ = orch.Shutdown(outerCtx, 20*time.Millisecond) + elapsed := time.Since(start) + + select { + case <-graceObserved: + default: + t.Fatal("dispatch context was never cancelled; grace phase did not run") + } + // Shutdown must have waited past the drain window (the grace phase happened), + // but well within the outer budget. + if elapsed < 20*time.Millisecond { + t.Errorf("Shutdown returned in %v, before the drain window elapsed; grace phase was skipped", elapsed) + } + if elapsed >= time.Second { + t.Errorf("Shutdown waited %v, at/over the full outer budget", elapsed) + } +} + +type ctxCapturingDispatcher struct { + started chan struct{} + release chan struct{} + ctxSeen chan context.Context +} + +func (d *ctxCapturingDispatcher) Dispatch(ctx context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + d.ctxSeen <- ctx + close(d.started) + <-d.release + return &model.Campaign{PlatformCampaignID: "pc-" + string(p), Status: "active", CampaignName: "n"}, nil +} + +// TestOrchestrator_NoDispatcherDoesNotLeavePendingClaim verifies that when no +// dispatcher is registered, no pending claim row is left behind (which would +// permanently block the pair). +func TestOrchestrator_NoDispatcherDoesNotLeavePendingClaim(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, nil) // no dispatchers + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + waitForTerminal(t, jobs, id) + camps.mu.Lock() + defer camps.mu.Unlock() + // No claim should have been inserted (dispatcher checked first), so existing + // is empty and no pending row blocks the pair. + if _, ok := camps.existing["b1|"+string(model.ProviderGoogleAds)]; ok { + t.Error("a pending claim row was left behind for a platform with no dispatcher") + } +} + +// preCreateErrDispatcher fails with an error that signals no upstream create. +type preCreateErr struct{} + +func (preCreateErr) Error() string { return "invalid input" } +func (preCreateErr) NoUpstreamCreate() bool { return true } + +type preCreateErrDispatcher struct{} + +func (preCreateErrDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, _ model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return nil, preCreateErr{} +} + +// TestOrchestrator_PreCreateErrorReleasesClaim verifies that a dispatcher error +// signalling no-upstream-create releases the claim (so the pair can be retried), +// unlike an ambiguous error which retains it. +func TestOrchestrator_PreCreateErrorReleasesClaim(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: preCreateErrDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + waitForTerminal(t, jobs, id) + camps.mu.Lock() + defer camps.mu.Unlock() + // The pre-create error should have released the pending claim. + if _, ok := camps.existing["b1|"+string(model.ProviderGoogleAds)]; ok { + t.Error("pre-create dispatcher error should have released the pending claim") + } +} + +// partialResultDispatcher exercises the platform clients' partial-result +// contract: it returns a non-nil campaign carrying the created upstream id +// ALONGSIDE a (non-pre-create) error, as reddit/twitter clients do when the +// campaign POST succeeded but a later step failed. +type partialResultDispatcher struct{} + +func (partialResultDispatcher) Dispatch(_ context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + return &model.Campaign{PlatformCampaignID: "pc-orphan-" + string(p), Status: "active", CampaignName: "n"}, + errors.New("ad group creation failed after campaign was created") +} + +// TestOrchestrator_PartialDispatchErrorPersistsUpstreamID verifies that when +// Dispatch returns a partial campaign (a created upstream id) together with an +// error, the retained pending row is stamped with that upstream id so the +// orphaned upstream campaign is reconcilable — and the claim is NOT released. +func TestOrchestrator_PartialDispatchErrorPersistsUpstreamID(t *testing.T) { + jobs := newFakeJobRepo() + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{ + model.ProviderGoogleAds: partialResultDispatcher{}, + }) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobFailed { + t.Errorf("status = %s, want failed", j.Status) + } + camps.mu.Lock() + defer camps.mu.Unlock() + // The claim must be RETAINED (not released) — the upstream campaign may exist. + row, ok := camps.existing["b1|"+string(model.ProviderGoogleAds)] + if !ok { + t.Fatal("pending claim should be retained after a partial dispatch error, not released") + } + // The retained row must now carry the orphaned upstream id (reconcilable) and + // remain 'pending' (a recoverable orphan, not a completed campaign). + if row.PlatformCampaignID != "pc-orphan-"+string(model.ProviderGoogleAds) { + t.Errorf("retained row PlatformCampaignID = %q, want the orphaned upstream id", row.PlatformCampaignID) + } + if row.Status != "pending" { + t.Errorf("retained row Status = %q, want pending (recoverable orphan)", row.Status) + } + // The partial campaign must have been persisted via UpsertCampaign. + if len(camps.upserted) != 1 { + t.Errorf("upserted %d campaigns, want 1 (partial upstream id persisted)", len(camps.upserted)) + } +} + +// blockingSweepJobRepo blocks inside FailStuckJobs until its context is +// cancelled, letting a test prove that cancelling the sweeper's context +// interrupts an in-flight sweep promptly (rather than the sweep running to its +// own timeout against a closing pool). +type blockingSweepJobRepo struct { + fakeJobRepo + entered chan struct{} +} + +func (r *blockingSweepJobRepo) FailStuckJobs(ctx context.Context, _ string) (int64, error) { + select { + case r.entered <- struct{}{}: + default: + } + <-ctx.Done() // block until the sweeper's context is cancelled + return 0, ctx.Err() +} + +// TestOrchestrator_SweeperInterruptedOnShutdown verifies that a sweep already +// blocked in the DB is interrupted PROMPTLY when Shutdown cancels the sweeper's +// dedicated context, and that Shutdown still completes within budget. Uses a +// tiny sweep interval so a sweep starts quickly, and a repo whose FailStuckJobs +// blocks until its context is cancelled. +func TestOrchestrator_SweeperInterruptedOnShutdown(t *testing.T) { + jobs := &blockingSweepJobRepo{ + fakeJobRepo: fakeJobRepo{jobs: map[string]*model.CampaignJob{}}, + entered: make(chan struct{}, 1), + } + camps := &fakeCampaignRepo{} + orch := NewOrchestrator(camps, jobs, nil) + // Drive the sweeper on a very short interval so a sweep begins promptly. The + // sweeper reads sweeperCtx, so overriding the interval doesn't affect the + // cancellation path under test. + orch.sweeperCtx, orch.sweeperCancel = context.WithCancel(context.Background()) + orch.wg.Add(1) + go func() { + defer orch.wg.Done() + ticker := time.NewTicker(time.Millisecond) + defer ticker.Stop() + for { + select { + case <-orch.sweeperCtx.Done(): + return + case <-ticker.C: + sctx, cancel := context.WithTimeout(orch.sweeperCtx, jobFinalizeTimeout) + _, _ = jobs.FailStuckJobs(sctx, "x") + cancel() + } + } + }() + + // Wait until a sweep is actually blocked inside FailStuckJobs. + select { + case <-jobs.entered: + case <-time.After(time.Second): + t.Fatal("sweep never started") + } + + // Shutdown must cancel the sweeper's context (interrupting the blocked sweep) + // and return quickly — well under the jobFinalizeTimeout the sweep would + // otherwise wait for. + done := make(chan error, 1) + start := time.Now() + go func() { done <- orch.Shutdown(context.Background(), 5*time.Second) }() + select { + case err := <-done: + if err != nil { + t.Errorf("Shutdown err = %v, want nil", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Shutdown did not return; blocked sweep was not interrupted") + } + if elapsed := time.Since(start); elapsed >= jobFinalizeTimeout { + t.Errorf("Shutdown took %v (>= jobFinalizeTimeout %v); sweep was not interrupted promptly", elapsed, jobFinalizeTimeout) + } +} + +// ctxAssertingCampaignRepo asserts UpsertCampaign is invoked with a live (not +// cancelled) context — proving the post-provider persist runs on a context +// detached from the cancelled dispatch context. +type ctxAssertingCampaignRepo struct { + fakeCampaignRepo + upsertCtxErr error // context error observed inside UpsertCampaign + upsertCalled chan struct{} +} + +func (r *ctxAssertingCampaignRepo) UpsertCampaign(ctx context.Context, c *model.Campaign) (*model.Campaign, error) { + r.mu.Lock() + r.upsertCtxErr = ctx.Err() + r.mu.Unlock() + close(r.upsertCalled) + return r.fakeCampaignRepo.UpsertCampaign(ctx, c) +} + +// TestOrchestrator_PersistSurvivesDispatchCancel verifies that a provider result +// completing AFTER the dispatch context is cancelled (the phase-two shutdown +// grace) is still persisted: the upsert must run on a detached context, not the +// cancelled dispatch context, so the record of the created upstream campaign is +// not lost. +func TestOrchestrator_PersistSurvivesDispatchCancel(t *testing.T) { + jobs := newFakeJobRepo() + camps := &ctxAssertingCampaignRepo{upsertCalled: make(chan struct{})} + ctxSeen := make(chan context.Context, 1) + // The dispatcher returns its campaign only after observing cancellation, so the + // persist step necessarily runs while the dispatch ctx is already cancelled. + disp := &cancelThenReturnDispatcher{ctxSeen: ctxSeen} + orch := NewOrchestrator(camps, jobs, map[model.Provider]PlatformDispatcher{model.ProviderGoogleAds: disp}) + brief := &model.CampaignBrief{ID: "b1", ProjectID: "cncf"} + id, _ := orch.Start(context.Background(), brief, brief.Version, []model.Provider{model.ProviderGoogleAds}, nil) + <-ctxSeen // dispatch is in-flight + + // Drain with an already-past deadline so Shutdown immediately cancels the run's + // context, but give a real outer budget so the grace phase lets it finish. + outerCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + go func() { _ = orch.Shutdown(outerCtx, time.Millisecond) }() + + // The upsert must be reached and must see a LIVE context (detached), then the + // job reaches a terminal state with the campaign persisted. + select { + case <-camps.upsertCalled: + case <-time.After(2 * time.Second): + t.Fatal("UpsertCampaign was never called; persist did not survive dispatch cancel") + } + camps.mu.Lock() + upsertErr := camps.upsertCtxErr + upsertCount := len(camps.upserted) + camps.mu.Unlock() + if upsertErr != nil { + t.Errorf("UpsertCampaign ran on a cancelled context (%v); persist must use a detached context", upsertErr) + } + if upsertCount != 1 { + t.Errorf("persisted %d campaigns, want 1 (created upstream campaign must be recorded)", upsertCount) + } + j := waitForTerminal(t, jobs, id) + if j.Status != model.JobSucceeded { + t.Errorf("status = %s, want succeeded", j.Status) + } +} + +// cancelThenReturnDispatcher waits until its context is cancelled, then returns a +// successful campaign — forcing the orchestrator's persist step to run while the +// dispatch context is already cancelled. +type cancelThenReturnDispatcher struct { + ctxSeen chan context.Context +} + +func (d *cancelThenReturnDispatcher) Dispatch(ctx context.Context, _ *model.CampaignBrief, p model.Provider, _ json.RawMessage) (*model.Campaign, error) { + d.ctxSeen <- ctx + <-ctx.Done() // return only after Shutdown cancels the dispatch context + return &model.Campaign{PlatformCampaignID: "pc-" + string(p), Status: "active", CampaignName: "n"}, nil +} + +// TestBriefETagIsQuoted verifies the emitted ETag is a quoted entity-tag. +func TestBriefETagIsQuoted(t *testing.T) { + if got := briefETag(3); got != `"3"` { + t.Errorf("briefETag(3) = %q, want \"3\"", got) + } + // And the parser round-trips it. + v, err := parseBriefIfMatch(strPtr(briefETag(7))) + if err != nil || v != 7 { + t.Errorf("round-trip of briefETag(7) = %d, %v; want 7, nil", v, err) + } +} + +func strPtr(s string) *string { return &s }