{"id":40016907,"url":"https://github.com/dmoruzzi/sfq","last_synced_at":"2026-01-25T06:15:47.096Z","repository":{"id":268116259,"uuid":"896714404","full_name":"dmoruzzi/sfq","owner":"dmoruzzi","description":"Salesforce Query (sfq) Wrapper","archived":false,"fork":false,"pushed_at":"2026-01-19T09:46:52.000Z","size":1273,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-19T11:37:57.003Z","etag":null,"topics":["salesforce-api","salesforce-rest-api"],"latest_commit_sha":null,"homepage":"https://sfq.dmoruzzi.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmoruzzi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-12-01T05:21:20.000Z","updated_at":"2026-01-19T09:46:36.000Z","dependencies_parsed_at":"2024-12-14T13:27:49.811Z","dependency_job_id":"0ea4b198-df0f-45bd-b986-a82923660c7f","html_url":"https://github.com/dmoruzzi/sfq","commit_stats":null,"previous_names":["dmoruzzi/sfq"],"tags_count":54,"template":false,"template_full_name":null,"purl":"pkg:github/dmoruzzi/sfq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmoruzzi%2Fsfq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmoruzzi%2Fsfq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmoruzzi%2Fsfq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmoruzzi%2Fsfq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmoruzzi","download_url":"https://codeload.github.com/dmoruzzi/sfq/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmoruzzi%2Fsfq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28745840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T05:12:38.112Z","status":"ssl_error","status_checked_at":"2026-01-25T05:04:50.338Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["salesforce-api","salesforce-rest-api"],"created_at":"2026-01-19T03:05:23.680Z","updated_at":"2026-01-25T06:15:47.089Z","avatar_url":"https://github.com/dmoruzzi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sfq (Salesforce Query)\n\nsfq is a lightweight Python wrapper library designed to simplify querying Salesforce, reducing repetitive code for accessing Salesforce data.\n\nFor more varied workflows, consider using an alternative like [Simple Salesforce](https://simple-salesforce.readthedocs.io/en/stable/). This library was even referenced on the [Salesforce Developers Blog](https://developer.salesforce.com/blogs/2021/09/how-to-automate-data-extraction-from-salesforce-using-python).\n\n## Features\n\n- Simplified query execution for Salesforce instances.\n- Integration with Salesforce authentication via refresh tokens.\n- Option to interact with Salesforce Tooling API for more advanced queries.\n- Platform Events support (list available \u0026 publish single/batch).\n  \n## Installation\n\nYou can install the `sfq` library using `pip`:\n\n```bash\npip install sfq\n```\n\n## Usage\n\n### Library Querying\n\n```python\nfrom sfq import SFAuth\n\n# Initialize the SFAuth class with authentication details\nsf = SFAuth(\n    instance_url=\"https://example-dev-ed.trailblaze.my.salesforce.com\",\n    client_id=\"your-client-id-here\",\n    client_secret=\"your-client-secret-here\",\n    refresh_token=\"your-refresh-token-here\"\n)\n\n# Execute a query to fetch account records\nprint(sf.query(\"SELECT Id FROM Account LIMIT 5\"))\n\n# Execute a query to fetch Tooling API data\nprint(sf.tooling_query(\"SELECT Id, FullName, Metadata FROM SandboxSettings LIMIT 5\"))\n```\n\n### Composite Batch Queries\n\n```python\nmultiple_queries = {\n    \"Recent Users\": \"\"\"\n        SELECT Id, Name,CreatedDate\n        FROM User\n        ORDER BY CreatedDate DESC\n        LIMIT 10\"\"\",\n    \"Recent Accounts\": \"SELECT Id, Name, CreatedDate FROM Account ORDER BY CreatedDate DESC LIMIT 10\",\n    \"Frozen Users\": \"SELECT Id, UserId FROM UserLogin WHERE IsFrozen = true\",  # If exceeds 2000 records, will paginate\n}\n\nbatched_response = sf.cquery(multiple_queries)\n\nfor subrequest_identifer, subrequest_response in batched_response.items():\n    print(f'\"{subrequest_identifer}\" returned {subrequest_response[\"totalSize\"]} records')\n\u003e\u003e\u003e \"Recent Users\" returned 10 records\n\u003e\u003e\u003e \"Recent Accounts\" returned 10 records\n\u003e\u003e\u003e \"Frozen Users\" returned 4082 records\n```\n\n### Collection Deletions\n\n```python\nresponse = sf.cdelete(['07La0000000bYgj', '07La0000000bYgk', '07La0000000bYgl'])\n\u003e\u003e\u003e [{'id': '07La0000000bYgj', 'success': True, 'errors': []}, {'id': '07La0000000bYgk', 'success': True, 'errors': []}, {'id': '07La0000000bYgl', 'success': True, 'errors': []}]\n```\n\n### Static Resources\n\n```python\npage = sf.read_static_resource_id('081aj000009jUMXAA2')\nprint(f'Initial resource: {page}')\n\u003e\u003e\u003e Initial resource: \u003ch1\u003eIt works!\u003c/h1\u003e\nsf.update_static_resource_name('HelloWorld', '\u003ch1\u003eHello World\u003c/h1\u003e')\npage = sf.read_static_resource_name('HelloWorld')\nprint(f'Updated resource: {page}')\n\u003e\u003e\u003e Updated resource: \u003ch1\u003eHello World\u003c/h1\u003e\nsf.update_static_resource_id('081aj000009jUMXAA2', '\u003ch1\u003eIt works!\u003c/h1\u003e')\n```\n\n### sObject Key Prefixes\n\n```python\n# Key prefix via IDs\nprint(sf.get_sobject_prefixes())\n\u003e\u003e\u003e {'0Pp': 'AIApplication', '6S9': 'AIApplicationConfig', '9qd': 'AIInsightAction', '9bq': 'AIInsightFeedback', '0T2': 'AIInsightReason', '9qc': 'AIInsightValue', ...}\n\n# Key prefix via names\nprint(sf.get_sobject_prefixes(key_type=\"name\"))\n\u003e\u003e\u003e {'AIApplication': '0Pp', 'AIApplicationConfig': '6S9', 'AIInsightAction': '9qd', 'AIInsightFeedback': '9bq', 'AIInsightReason': '0T2', 'AIInsightValue': '9qc', ...}\n```\n\n### Platform Events\n\nPlatform Events allow publishing and subscribing to real-time events. Requires a custom Platform Event (e.g., 'sfq__e' with fields like 'text__c').\n\n```python\nfrom sfq import SFAuth\n\nsf = SFAuth(\n    instance_url=\"https://example-dev-ed.trailblaze.my.salesforce.com\",\n    client_id=\"your-client-id-here\",\n    client_secret=\"your-client-secret-here\",\n    refresh_token=\"your-refresh-token-here\"\n)\n\n# List available events\nevents = sf.list_events()\nprint(events)  # e.g., ['sfq__e']\n\n# Publish single event\nresult = sf.publish('sfq__e', {'text__c': 'Hello Event!'})\nprint(result)  # {'success': True, 'id': '2Ee...'}\n\n# Publish batch\nevents_data = [\n    {'text__c': 'Batch 1 message'},\n    {'text__c': 'Batch 2 message'}\n]\nbatch_result = sf.publish_batch(events_data, 'sfq__e')\nprint(batch_result['results'])  # List of results\n\n## How to Obtain Salesforce Tokens\n\nTo use the `sfq` library, you'll need a **client ID** and **refresh token**. The easiest way to obtain these is by using the Salesforce CLI:\n\n### Steps to Get Tokens\n\n1. **Install the Salesforce CLI**:  \n   Follow the instructions on the [Salesforce CLI installation page](https://developer.salesforce.com/tools/salesforcecli).\n   \n2. **Authenticate with Salesforce**:  \n   Login to your Salesforce org using the following command:\n   \n   ```bash\n   sf org login web --alias int --instance-url https://corpa--int.sandbox.my.salesforce.com\n   ```\n   \n3. **Display Org Details**:  \n   To get the client ID, client secret, refresh token, and instance URL, run:\n   \n   ```bash\n   sf org display --target-org int --verbose --json\n   ```\n\n   The output will look like this:\n\n   ```json\n   {\n     \"status\": 0,\n     \"result\": {\n       \"id\": \"00Daa0000000000000\",\n       \"apiVersion\": \"63.0\",\n       \"accessToken\": \"00Daa0000000000000!evaU3fYZEWGUrqI5rMtaS8KYbHfeqA7YWzMgKToOB43Jk0kj7LtiWCbJaj4owPFQ7CqpXPAGX1RDCHblfW9t8cNOCNRFng3o\",\n       \"instanceUrl\": \"https://example-dev-ed.trailblaze.my.salesforce.com\",\n       \"username\": \"user@example.com\",\n       \"clientId\": \"PlatformCLI\",\n       \"connectedStatus\": \"Connected\",\n       \"sfdxAuthUrl\": \"force://PlatformCLI::nwAeSuiRqvRHrkbMmCKvLHasS0vRbh3Cf2RF41WZzmjtThnCwOuDvn9HObcUjKuTExJPqPegIwnLB5aH6GNWYhU@example-dev-ed.trailblaze.my.salesforce.com\",\n       \"alias\": \"int\"\n     }\n   }\n   ```\n\n4. **Extract and Use the Tokens**:  \n   The `sfdxAuthUrl` is structured as:\n   \n   ```\n   force://\u003cclient_id\u003e:\u003cclient_secret\u003e:\u003crefresh_token\u003e@\u003cinstance_url\u003e\n   ```\n\n   This means with the above output sample, you would use the following information:\n\n   ```python\n   # This is for illustrative purposes; use environment variables instead\n   client_id = \"PlatformCLI\"\n   client_secret = \"\"\n   refresh_token = \"nwAeSuiRqvRHrkbMmCKvLHasS0vRbh3Cf2RF41WZzmjtThnCwOuDvn9HObcUjKuTExJPqPegIwnLB5aH6GNWYhU\"\n   instance_url = \"https://example-dev-ed.trailblaze.my.salesforce.com\"\n\n   from sfq import SFAuth\n   sf = SFAuth(\n       instance_url=instance_url,\n       client_id=client_id,\n       client_secret=client_secret,\n       refresh_token=refresh_token,\n   )\n\n   ```\n\n## Important Considerations\n\n- **Security**: Safeguard your client_id, client_secret, and refresh_token diligently, as they provide access to your Salesforce environment. Avoid sharing or exposing them in unsecured locations.\n- **Efficient Data Retrieval**: The `query` and `cquery` function automatically handles pagination, simplifying record retrieval across large datasets. It's recommended to use the `LIMIT` clause in queries to control the volume of data returned.\n- **Advanced Tooling Queries**: Utilize the `tooling_query` function to access the Salesforce Tooling API. This option is designed for performing complex operations, enhancing your data management capabilities.\n\n## Telemetry\n\n`sfq` includes an **enhanced HTTP event telemetry system** to gather usage insights and diagnostics. Telemetry is **enabled by default** to help improve the library, but you can disable it if you prefer.\n\n### Configuration\n\n| Variable                     | Description                                                     | Default                                           |\n|------------------------------|-----------------------------------------------------------------|---------------------------------------------------|\n| `SFQ_TELEMETRY`              | `0` (disabled), `1` (Standard), `2` (Debug), `-1` (Full)        | `1` (Standard)                                    |\n| `SFQ_TELEMETRY_ENDPOINT`     | URL to POST telemetry events                                    | Grafana Cloud Loki endpoint                       |\n| `SFQ_TELEMETRY_SAMPLING`     | Fraction of events to send (`0.0`–`1.0`)                        | `1.0`                                             |\n| `SFQ_TELEMETRY_KEY`          | Optional bearer token for the telemetry endpoint                | None                                              |\n| `SFQ_GRAFANACLOUD_URL`       | URL to fetch credentials JSON, or base64 encoded credentials JSON | Public credentials endpoint                       |\n| `DD_API_KEY`                 | Override DataDog API key (for security)                         | None (uses credentials file)                      |\n| `DD_SOURCE`                  | Override DataDog source field                                   | `\"salesforce\"`                                   |\n| `DD_SERVICE`                 | Override DataDog service field                                  | `\"salesforce\"`                                   |\n| `DD_TAGS`                    | Override DataDog tags (comma-separated key:value pairs)        | `\"source:salesforce\"`                            |\n\n### Telemetry Levels\n\n* **Disabled (`0`)**:\n  No telemetry events are sent.\n\n* **Standard (`1`)** *(default)*:\n  Sends **anonymized, non-PII events** to Grafana Cloud including method names, status codes, and execution duration. Safe for general usage.\n\n* **Debug (`2`)**:\n  Sends **additional diagnostic information** and forwards log records from the library. May include sensitive data (tokens, IDs, PII, stack traces). **Do not enable Debug telemetry against public endpoints.**\n\n* **Full (`-1`)** *(undocumented, internal only)*:\n  Sends **complete request/response data** including bodies and headers. Only for internal corporate networks with proper security controls.\n\n### Telemetry Destinations\n\nThe enhanced telemetry system supports multiple destinations:\n\n1. **Grafana Cloud Loki** (default): Standard and Debug telemetry is sent to Grafana Cloud for visualization and analysis.\n\n2. **DataDog Logs**: Telemetry can be sent to DataDog logs endpoint for monitoring and analysis.\n\n3. **Salesforce Telemetry Endpoint**: When available, telemetry can also be sent directly to Salesforce endpoints using the active session's access token.\n\n### Privacy \u0026 Security\n\n* **Opt-out**: You can disable telemetry by setting `SFQ_TELEMETRY=0`.\n* **Standard events** do **not** include request bodies, tokens, or user/org identifiers.\n* **Debug diagnostics** are intended for internal use **only**. Route them to a trusted internal endpoint.\n* **Full transparency mode** should only be used in secure, internal networks.\n* Review retention and access controls on any telemetry receiver.\n\n### Usage Examples\n\n**Disable telemetry entirely:**\n```bash\nexport SFQ_TELEMETRY=0\n```\n\n**Enable Debug telemetry for troubleshooting:**\n```bash\nexport SFQ_TELEMETRY=2\nexport SFQ_TELEMETRY_ENDPOINT=https://your-internal-telemetry-endpoint.com\n```\n\n**Custom Grafana Cloud credentials:**\n```bash\nexport SFQ_GRAFANACLOUD_URL=https://your-grafana-credentials-endpoint.com/creds.json\n```\n\n**DataDog Logs Configuration:**\nTo use DataDog as your telemetry destination, provide DataDog credentials JSON:\n```bash\nexport SFQ_GRAFANACLOUD_URL=https://your-datadog-credentials-endpoint.com/creds.json\n```\n\nWith DataDog credentials JSON format:\n```json\n{\n  \"URL\": \"https://http-intake.logs.us3.datadoghq.com/api/v2/logs\",\n  \"DD_API_KEY\": \"your_datadog_api_key_here\",\n  \"PROVIDER\": \"DATADOG\"\n}\n```\n\n**DataDog Environment Variable Overrides:**\n```bash\n# Override DataDog API key (recommended for security)\nexport DD_API_KEY=\"your_production_datadog_api_key\"\n\n# Customize DataDog fields\nexport DD_SOURCE=\"custom_app_name\"\nexport DD_SERVICE=\"api_service\"\nexport DD_TAGS=\"env:production,team:backend,region:us-east\"\n```\n\n**Base64 encoded credentials:**\nInstead of providing a URL, you can also provide base64 encoded credentials JSON:\n```bash\nexport SFQ_GRAFANACLOUD_URL=\"$(echo '{\"URL\": \"https://your-loki-endpoint.com/loki/api/v1/push\", \"USER_ID\": \"1234567\", \"API_KEY\": \"your-api-key-here\"}' | base64 -w 0)\"\n```\n\nFor DataDog base64 credentials:\n```bash\nexport SFQ_GRAFANACLOUD_URL=\"$(echo '{\"URL\": \"https://http-intake.logs.us3.datadoghq.com/api/v2/logs\", \"DD_API_KEY\": \"your_api_key\", \"PROVIDER\": \"DATADOG\"}' | base64 -w 0)\"\n```\n\n**Reduce telemetry volume (sample 10% of events):**\n```bash\nexport SFQ_TELEMETRY_SAMPLING=0.1\n```\n\n\n### Log Samples\n\nHere are examples of telemetry log entries at different levels:\n\n**Standard Telemetry Event (Grafana Cloud):**\nThis includes anonymized, non-PII fields (method, status code, duration). Intended for general opt-in use.\n\n```json\n{\n    \"timestamp\": \"2026-01-19T09:21:05Z\",\n    \"sdk\": \"sfq\",\n    \"sdk_version\": \"0.0.53\",\n    \"event_type\": \"http.request\",\n    \"client_id\": \"c86f259c69db106c1a28d28751196036ae884bcf93e8282657bd4228e06e5897\",\n    \"telemetry_level\": 1,\n    \"trace_id\": \"5d280fca-bb04-45a9-8d1b-929c6d33edfa\",\n    \"span\": \"default\",\n    \"log_level\": \"INFO\",\n    \"payload\": {\n        \"method\": \"GET\",\n        \"status_code\": 200,\n        \"duration_ms\": 174,\n        \"environment\": {\n            \"os\": \"Windows\",\n            \"os_release\": \"11\",\n            \"python_version\": \"3.14.2\",\n            \"sforce_client\": \"sfq/0.0.53\"\n        }\n    }\n}```\n\n**DataDog Logs Format:**\nWhen telemetry is sent to DataDog, it uses the DataDog logs format:\n\n```json\n{\n    \"ddsource\": \"salesforce\",\n    \"service\": \"salesforce\",\n    \"hostname\": \"https://example.my.salesforce.com\",\n    \"message\": {\n      \"timestamp\": \"2026-01-19T09:21:05Z\",\n      \"sdk\": \"sfq\",\n      \"sdk_version\": \"0.0.53\",\n      \"event_type\": \"http.request\",\n      \"client_id\": \"c86f259c69db106c1a28d28751196036ae884bcf93e8282657bd4228e06e5897\",\n      \"telemetry_level\": 1,\n      \"trace_id\": \"5d280fca-bb04-45a9-8d1b-929c6d33edfa\",\n      \"span\": \"default\",\n      \"log_level\": \"INFO\",\n      \"payload\": {\n        \"method\": \"GET\",\n        \"status_code\": 200,\n        \"duration_ms\": 174,\n        \"environment\": {\n          \"os\": \"Windows\",\n          \"os_release\": \"11\",\n          \"python_version\": \"3.14.2\",\n          \"sforce_client\": \"sfq/0.0.53\"\n        }\n      }\n    },\n    \"ddtags\": \"source:salesforce\"\n}\n```\n\n**Salesforce Telemetry Event:**\nWhen telemetry is sent to Salesforce endpoints, it includes Salesforce-specific fields.\n\n```json\n{\n    \"timestamp\": \"2025-12-29T03:48:06Z\",\n    \"sdk\": \"sfq\",\n    \"sdk_version\": \"0.0.47\",\n    \"event_type\": \"http.request\",\n    \"client_id\": \"c302d04df42738b23dbfe59688fac06367b768e180d9dfb4794a99cab41dad78\",\n    \"telemetry_level\": 1,\n    \"trace_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n    \"span\": \"default\",\n    \"log_level\": \"INFO\",\n    \"payload\": {\n        \"method\": \"GET\",\n        \"endpoint\": \"/services/data/v56.0/query\",\n        \"status\": 200,\n        \"duration_ms\": 123,\n        \"access_token\": \"00Dxx0000000000!AQEAQ...\",\n        \"instance_url\": \"https://example.my.salesforce.com\",\n        \"environment\": {\n            \"os\": \"Windows\",\n            \"os_release\": \"10\",\n            \"python_version\": \"3.11.14\",\n            \"user_agent\": \"sfq/0.0.47\",\n            \"sforce_client\": \"sfq/0.0.47\"\n        }\n    }\n}\n```\n\n**Debug Telemetry Event:**\nThis includes detailed request/response data and diagnostics. This is intended for opt-in use only.\n\n```json\n{\n    \"timestamp\": \"2026-01-19T09:21:57Z\",\n    \"sdk\": \"sfq\",\n    \"sdk_version\": \"0.0.48\",\n    \"event_type\": \"http.request\",\n    \"client_id\": \"57b550c8201c70579782caeb5ef6aa7ad8ba024e4f9d10cdcec1aa8086f0d4ee\",\n    \"telemetry_level\": 2,\n    \"trace_id\": \"f42510bb-48b5-4636-9f6e-c12c578d2de6\",\n    \"span\": \"default\",\n    \"log_level\": \"DEBUG\",\n    \"payload\": {\n        \"method\": \"GET\",\n        \"status\": 200,\n        \"duration_ms\": 187,\n        \"request_headers\": {\n            \"User-Agent\": \"sfq/0.0.53\",\n            \"Sforce-Call-Options\": \"client=sfq/0.0.53\",\n            \"Accept\": \"application/json\",\n            \"Content-Type\": \"application/json\",\n            \"Authorization\": \"REDACTED\"\n        },\n        \"environment\": {\n            \"os\": \"Windows\",\n            \"os_release\": \"11\",\n            \"python_version\": \"3.14.2\",\n            \"user_agent\": \"sfq/0.0.53\",\n            \"sforce_client\": \"sfq/0.0.53\"\n        },\n        \"path_hash\": \"119a7bffe38631d987935f5f88effb89fe9267993fa4b459f712a993ef5859f0\"\n    }\n}```\n\n**Full Telemetry Event:**\nThis includes complete request/response bodies and headers. Intended for internal corporate networks only.\n\n```json\n{\n    \"timestamp\": \"2026-01-19T09:23:19Z\",\n    \"sdk\": \"sfq\",\n    \"sdk_version\": \"0.0.53\",\n    \"event_type\": \"http.request\",\n    \"client_id\": \"2a67a773d8cb1137ab1dfe6ad579217cc3fbbb3c36103350b38fa06316edc674\",\n    \"telemetry_level\": -1,\n    \"trace_id\": \"28e48c34-19c5-4bc3-80a8-d5eaa892ac6d\",\n    \"span\": \"default\",\n    \"log_level\": \"DEBUG\",\n    \"payload\": {\n        \"method\": \"GET\",\n        \"endpoint\": \"/services/data/v65.0/query?q=SELECT Id FROM Organization LIMIT 1\",\n        \"status\": 200,\n        \"duration_ms\": 181,\n        \"request_headers\": {\n            \"User-Agent\": \"sfq/0.0.53\",\n            \"Sforce-Call-Options\": \"client=sfq/0.0.53\",\n            \"Accept\": \"application/json\",\n            \"Content-Type\": \"application/json\",\n            \"Authorization\": \"********\"\n        },\n        \"response_body\": {\n            \"totalSize\": 1,\n            \"done\": true,\n            \"records\": [\n                {\n                    \"attributes\": {\n                        \"type\": \"Organization\",\n                        \"url\": \"/services/data/v65.0/sobjects/Organization/00Daa0000000000000\"\n                    },\n                    \"Id\": \"00Daa0000000000000\"\n                }\n            ]\n        },\n        \"response_headers\": {\n            \"Date\": \"Mon, 19 Jan 2026 09:23:19 GMT\",\n            \"Vary\": \"Accept-Encoding\",\n            \"Set-Cookie\": \"********\",\n            \"X-Content-Type-Options\": \"nosniff\",\n            \"Strict-Transport-Security\": \"max-age=63072000; includeSubDomains\",\n            \"X-Robots-Tag\": \"none\",\n            \"Cache-Control\": \"no-cache,must-revalidate,max-age=0,no-store,private\",\n            \"Sforce-Limit-Info\": \"api-usage=5718/15000\",\n            \"Content-Type\": \"application/json;charset=UTF-8\",\n            \"Transfer-Encoding\": \"chunked\"\n        },\n        \"environment\": {\n            \"os\": \"Windows\",\n            \"os_release\": \"11\",\n            \"python_version\": \"3.14.2\",\n            \"user_agent\": \"sfq/0.0.53\",\n            \"sforce_client\": \"sfq/0.0.53\"\n        }\n    }\n}\n```\n\nThe following fields appear in telemetry events; below are concise explanations to help you interpret them:\n\n- `timestamp`: ISO 8601 timestamp (UTC) when the event was generated.\n- `sdk`: The SDK name that generated the event (this library: `sfq`).\n- `sdk_version`: Version of the SDK (semantic version string).\n- `sfk_version`: Alias for `sdk_version` (included for compatibility with some consumers).\n- `event_type`: Logical event category (e.g., `http.request`, `log.record`).\n- `client_id`: Anonymous identifier generated **at runtime** for the current client instance (SHA-256 of a UUID). This ID is **not persisted** across runs and **cannot be traced** to any user or organization data. Its purpose is to provide a temporary, unique identifier for telemetry events.\n- `telemetry_level`: Telemetry level active for the client (0=disabled, 1=Standard, 2=Debug, -1=Full).\n- `trace_id`: Unique trace identifier for correlating related events.\n- `span`: Span identifier for distributed tracing.\n- `log_level`: Log level of the event (INFO for Standard, DEBUG for Debug/Full).\n\n- `payload.method`: HTTP method used (e.g., `GET`, `POST`, `PUT`, `DELETE`).\n- `payload.status` / `payload.status_code`: HTTP response status code (when available).\n- `payload.duration_ms`: Duration of the operation in milliseconds (when available).\n- `payload.endpoint`: (Salesforce telemetry only) The request endpoint path.\n- `payload.access_token`: (Salesforce telemetry only) Access token used for authentication.\n- `payload.instance_url`: (Salesforce telemetry only) Salesforce instance URL.\n- `payload.environment`: Environment summary containing:\n    - `os`: OS name (e.g., `Windows`, `Linux`).\n    - `os_release`: OS release string (e.g., `10`).\n    - `python_version`: Python runtime version (e.g., `3.11.14`).\n    - `user_agent`: (Debug/Full telemetry) User-Agent header value when available.\n    - `sforce_client`: extracted `client=` value from `Sforce-Call-Options` header when available.\n\n- `payload.path_hash` (Debug telemetry only): SHA-256 hash of the sanitized path string. The raw request path/URL is never included in Standard telemetry (level 1); Debug telemetry (level 2) includes only this hash to allow grouping without sending identifying path components.\n\n- `payload.request_headers` (Debug/Full telemetry): Request headers with sensitive information redacted.\n- `payload.response_headers` (Debug/Full telemetry): Response headers with sensitive information redacted.\n- `payload.response_body` (Full telemetry only): Complete response body with sensitive information redacted.\n\n### DataDog-Specific Fields\n\nWhen using DataDog as the telemetry destination, the following fields are included:\n\n- `ddsource`: Source of the log (default: \"salesforce\", configurable via `DD_SOURCE` environment variable)\n- `service`: Service name (default: \"salesforce\", configurable via `DD_SERVICE` environment variable)\n- `hostname`: Hostname or identifier (uses Salesforce `instance_url` for debug levels, `org_id` for standard level)\n- `message`: JSON string containing the complete telemetry event payload\n- `ddtags`: Comma-separated key:value tags (default: \"source:salesforce\", configurable via `DD_TAGS` environment variable)\n\n### Grafana Cloud Format\n\nWhen sending to Grafana Cloud Loki, events are wrapped in the Loki format:\n\n- `streams`: Array containing stream objects.\n- `streams[].stream`: Stream labels/metadata including SDK info and telemetry level.\n- `streams[].values`: Array of `[timestamp_ns, json_event]` pairs.\n\nIf you need more detail for debugging, enable Debug telemetry (`SFQ_TELEMETRY=2`) and route events to a trusted internal endpoint via `SFQ_TELEMETRY_ENDPOINT`. To disable telemetry entirely, set `SFQ_TELEMETRY=0`.\n\n## CI-Aware HTTP Header Attachment\n\n`sfq` automatically attaches traceable CI metadata to outbound HTTP requests when running in CI environments. This enables request tracking and correlation across systems through the `AdditionalInfo` fields for ApiEvent and LoginEvent.\n\n### How It Works\n\nWhen running in a CI environment, `sfq` automatically detects the CI provider and attaches non-PII metadata headers to all HTTP requests. \n\n### Supported CI Providers\n\n| CI Provider    | Detection Variable | Value  |\n|----------------|--------------------|--------|\n| GitHub Actions | `GITHUB_ACTIONS`   | `true` |\n| GitLab CI      | `GITLAB_CI`        | `true` |\n| CircleCI       | `CIRCLECI`         | `true` |\n\n### Header Format\n\nAll CI metadata uses the `x-sfdc-addinfo-` prefix:\n\n```\nx-sfdc-addinfo-ci_provider: github\nx-sfdc-addinfo-run_id: 123456\nx-sfdc-addinfo-repository: org_repo\nx-sfdc-addinfo-workflow: Release\nx-sfdc-addinfo-ref: refs_heads_main\nx-sfdc-addinfo-runner_os: Linux\n```\n\n### Non-PII Metadata\n\nThe following metadata is automatically included when available:\n\n**GitHub Actions:**\n- `GITHUB_RUN_ID` → `x-sfdc-addinfo-run_id`\n- `GITHUB_REPOSITORY` → `x-sfdc-addinfo-repository`\n- `GITHUB_WORKFLOW` → `x-sfdc-addinfo-workflow`\n- `GITHUB_REF` → `x-sfdc-addinfo-ref`\n- `RUNNER_OS` → `x-sfdc-addinfo-runner_os`\n\n**GitLab CI:**\n- `CI_PIPELINE_ID` → `x-sfdc-addinfo-pipeline_id`\n- `CI_PROJECT_PATH` → `x-sfdc-addinfo-project_path`\n- `CI_JOB_NAME` → `x-sfdc-addinfo-job_name`\n- `CI_COMMIT_REF_NAME` → `x-sfdc-addinfo-commit_ref_name`\n- `CI_RUNNER_ID` → `x-sfdc-addinfo-runner_id`\n\n**CircleCI:**\n- `CIRCLE_WORKFLOW_ID` → `x-sfdc-addinfo-workflow_id`\n- `CIRCLE_PROJECT_REPONAME` → `x-sfdc-addinfo-project_reponame`\n- `CIRCLE_BRANCH` → `x-sfdc-addinfo-branch`\n- `CIRCLE_BUILD_NUM` → `x-sfdc-addinfo-build_num`\n\n### PII Metadata \n\nPII headers are not included by default. To include them, set the environment variable:\n\n```bash\nexport SFQ_ATTACH_CI_PII=true\n```\n\n**GitHub Actions PII:**\n- `GITHUB_ACTOR` → `x-sfdc-addinfo-pii-actor`\n- `GITHUB_ACTOR_ID` → `x-sfdc-addinfo-pii-actor_id`\n- `GITHUB_TRIGGERING_ACTOR` → `x-sfdc-addinfo-pii-triggering_actor`\n\n**GitLab CI PII:**\n- `GITLAB_USER_LOGIN` → `x-sfdc-addinfo-pii-user_login`\n- `GITLAB_USER_NAME` → `x-sfdc-addinfo-pii-user_name`\n- `GITLAB_USER_EMAIL` → `x-sfdc-addinfo-pii-user_email`\n- `GITLAB_USER_ID` → `x-sfdc-addinfo-pii-user_id`\n\n**CircleCI PII:**\n- `CIRCLE_USERNAME` → `x-sfdc-addinfo-pii-username`\n\n### Configuration\n\n| Variable            | Description                                   | Default |\n|---------------------|-----------------------------------------------|---------|\n| `SFQ_ATTACH_CI_PII` | Include PII headers (`true`, `1`, `yes`, `y`) | `false` |\n| `SFQ_ATTACH_CI`     | Include CI headers (`true`, `1`, `yes`, `y`)  | `true`  |\n\n## Custom Addinfo Headers\n\n`sfq` supports injecting arbitrary custom headers into HTTP requests using the `SFQ_HEADERS` environment variable. This allows you to add custom metadata to all API requests, which appears in the `AdditionalInfo` fields for ApiEvent and LoginEvent.\n\n### How It Works\n\nThe `SFQ_HEADERS` environment variable allows you to specify custom key-value pairs that will be converted into `x-sfdc-addinfo-` headers and attached to all HTTP requests.\n\n### Usage\n\nSet the `SFQ_HEADERS` environment variable using the format `key1:value1|key2:value2`:\n\n```bash\nexport SFQ_HEADERS=\"custom_key:custom_value|another_header:another_value\"\n```\n\nThis will create the following HTTP headers:\n\n```\nx-sfdc-addinfo-custom_key: custom_value\nx-sfdc-addinfo-another_header: another_value\n```\n\n### Example\n\n```python\nimport os\nfrom sfq import SFAuth\n\n# Set custom headers\nos.environ['SFQ_HEADERS'] = \"deployment_id:deploy-123|environment:production|team:data-engineering\"\n\n# Initialize SFAuth\nsf = SFAuth(\n    instance_url=\"https://example-dev-ed.trailblaze.my.salesforce.com\",\n    client_id=\"your-client-id-here\",\n    client_secret=\"your-client-secret-here\",\n    refresh_token=\"your-refresh-token-here\"\n)\n\n# All queries will now include the custom headers\nresult = sf.query(\"SELECT Id FROM Account LIMIT 5\")\n```\n\n### Features\n\n- **Multiple Headers**: Separate multiple key-value pairs with `|`\n- **Value Support**: Values can contain spaces, equals signs, and other special characters\n- **Empty Handling**: Empty keys or values are automatically ignored\n- **Automatic Integration**: Headers are automatically added to all HTTP requests\n\n### Configuration\n\n| Variable      | Description                                        | Default |\n|---------------|----------------------------------------------------|---------|\n| `SFQ_HEADERS` | Custom headers in format `key1:value1|key2:value2` | None    |\n\n### Use Cases\n\n- **Deployment Tracking**: Add deployment IDs to track which deployment made API calls\n- **Environment Identification**: Identify requests from different environments (dev, staging, prod)\n- **Team Attribution**: Track which team or service is making requests\n- **Custom Metadata**: Add any custom metadata needed for tracking and debugging\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmoruzzi%2Fsfq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmoruzzi%2Fsfq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmoruzzi%2Fsfq/lists"}