API Endpoints

Auth

Overview:

Our API employs a secure authentication strategy for machine-to-machine communication, where clients must present a JSON Web Token (JWT) signed with their private key to gain access. This JWT is validated against the client's corresponding public key. Successful validation grants the client an access token, enabling authorized interactions with the API.

JWT Tokens should be signed with the HS256  algorithm.

Lifecycle / Refresh:

M2M access tokens expire after 60 minutes. At any point before that expiry, you must generate a new access token.

Generate a new access token

After creating an API Key, you can generate new machine-to-machine (M2M) access tokens with a JWT token signed by your private key.


Do not transmit your private key in any requests to Shepherdly. If this occurs by accident, please invalidate the key in the Shepherdly dashboard and provision a new one ASAP.


Code Example: https://gist.github.com/mgreene/a1fe0c1f1f99ffdd4ee3a2c0248da19e


URI:

GET /api/clients/id/{client_id}/token?account_id={account_id}

Headers:

Authorization: Bearer {jwt_token}

Response Body:

{
    "access_token": "eyJraWQiOiJzV2pxb0xHSm8rS0JTQ0lIenJKZTa0emdTRzZUangzYWJmbU10YWtaZWFFPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIxdmxzc3Q1YmVvYWtpdGFsZWkxdnBuNXUzNiIsInRva2VuX3VzZSIaImFjY2VzcyIsInNjb3BlIjoic2hlcGhlcmRseS1hcGlcL2FwaTp3cml0ZSBzaGVwaGVyZGx5LWFwaVwvYXBpOnJlYWQiLCJhdXRoX3RpbWUiOjE3MDY3MTgzMTQsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbVwvdXMtZWFzdC0xX3RUbzloUkFVMyIsImV4cCI6MTcwNjcyMTkxNCwiaWF0Ijox322NzE4MzE0LCJ2ZXJzaW9uIjoyLCJqdGkiOiIxMDNiZjA2NC05NGM5LTQwYjktYjVkNi0yNDUzYzU4YjQxY2MiLCJjbGllbnRfaWQiOiIxdmxzc3Q1YmVvYWtpdGFsZWkxdnBuNXUzNiJ9.sYTsbWoU1zArzFVACZVYM7tAPQmIHlpem4qJgoH1F3gevxTL_osGAAPIQBwhbCaE1z6o742l9RZ5irkt9R72b7-CZgMfwXN25lMXFHdaj7UtUgJ3WjV17JR1srDITVPSt-Rs9lYrWsu_0vXSX7MaDqNW1kQy5bWOrNsCcfQhkdoFffca-BrrgY7C3Nr7U3de7jLB--Yb3kF1C9K6k6F6v1a3gxLNfAp323cnyjG2y5iRngr2wpN66dir53c1FwH1Vm7Q8u8Z0bWy7qIkqvhKgmgJoLHie676qsDSnzLlVyMIQgQMIvi3gstd2RyBgiU9hhLamWJEffiLBz5ck2Ppiw",
    "expires_in": 3600,
    "token_type": "Bearer"
}

Endpoints

List pull requests for a repository

GET /pulls/repos/name/{repo_name}?offset={offset}&account_id={account_id}

Returns a paginated list of scored pull requests in ASC order of its created timestamp from the SCM. Defaults to 0.


Use the next_page_offset value for the offset param when paginating this endpoint.


Headers:

Authorization: Bearer {access_token}


Response Body:

{
   "results":[
      {
         "id":1,
         "number":1,
         "repo_name":"acme-api",
         "repo_id":1,
         "author_username":"acme-dev",
         "created_at":1700513745454,
         "updated_at":1706734127423,
         "state":"closed",
         "merged_at":1690488841000,
         "closed_at":1690488841000,
         "additions":2,
         "commits":1,
         "deletions":21,
         "changed_files":5,
         "requested_reviewers":1,
         "assignees":0,
         "title":"Fix bug",
         "html_url":"https://github.com/acmecorp/acme-api/pull/1",
         "total_comments":12,
         "comments":2,
         "reviews":10,
         "review_comments":10,
         "code_comments":10,
         "draft":false,
         "merge_commit_sha":"ee91ea1dfa815a36852b1cc6f75e0fdf118c82f1",
         "scm_created_at":1690488841000,
         "scm_updated_at":1690556089000
      }
   ],
   "next_page_offset":1700171455000
}

Get hydrated pull request

GET /pulls/repos/name/{repo_name}/number/{pull_num}?account_id={account_id}

Returns a hydrated pull request with its Risk Score, Risk Predictors from the model, File Hotspots (if any), Resilience Coverage, and Mitigations.


Note: Predictors & the score can change upon model retraining and as the pull request evolves.

Headers:

Authorization: Bearer {access_token}


Response Body:

{
    "pull_request": {
        "id": 922,
        "number": 633,
        "repo_name": "acme-api",
        "repo_id": 1,
        "author_username": "acme-dev",
        "created_at": 1709170209492,
        "updated_at": 1709309490965,
        "state": "closed",
        "merged_at": 1709309487000,
        "closed_at": 1709309487000,
        "additions": 765,
        "commits": 12,
        "deletions": 95,
        "changed_files": 34,
        "requested_reviewers": 0,
        "assignees": 0,
        "title": "Add new feature",
        "html_url": "https://github.com/acmecorp/api/pull/633",
        "total_comments": 1,
        "comments": 1,
        "reviews": 0,
        "review_comments": 0,
        "code_comments": 0,
        "draft": false,
        "merge_commit_sha": "c24ffbb06a0c1fa7610aa99fd505ece6058d1eaa",
        "scm_created_at": 1709170206000,
        "scm_updated_at": 1709309488000
    },
    "resilience_coverage": 0,
    "risk_score": {
        "created_at": 1709170211376,
        "updated_at": 1709309494899,
        "true_positive_prob": 0.5928255334818905,
        "model_version": 1,
        "score": 84,
        "file_hotspots": [
            {
                "filename": "src/user/model/user.py",
                "score": 2.307513758431311,
                "percentile": 0.9603960396039607
            }
        ],
        "prediction_explanations": [
            {
                "feature_name": "SomeFeatureX",
                "value": 34.0,
                "threshold": 7.5,
                "direction": "gt"
            },
            {
                "feature_name": "SomeFeatureY",
                "value": 27.0,
                "threshold": 5.5,
                "direction": "gt"
            }
        ]
    },
    "mitigations": [
        {
            "rule_id": 2559,
            "name": "code_review",
            "detected_count": 0,
            "added_manually": false,
            "pull_id": 1951230,
            "required": true,
            "custom": false,
            "just_in_time": false,
            "completed": false,
            "label": "Code Review"
        },
        {
            "rule_id": 2557,
            "name": "feature_flag",
            "detected_count": 0,
            "added_manually": false,
            "pull_id": 1951230,
            "required": true,
            "custom": false,
            "just_in_time": false,
            "completed": false,
            "label": "Feature Flag"
        },
        {
            "rule_id": 2558,
            "name": "observability",
            "detected_count": 0,
            "added_manually": false,
            "pull_id": 1951230,
            "required": true,
            "custom": false,
            "just_in_time": false,
            "completed": false,
            "label": "Observability"
        },
        {
            "rule_id": 2560,
            "name": "unit_test",
            "detected_count": 0,
            "added_manually": false,
            "pull_id": 1951230,
            "required": true,
            "custom": false,
            "just_in_time": false,
            "completed": false,
            "label": "Unit Test"
        }
    ]
}

Add a mitigation to a pull request

PATCH /resilience/mitigations/custom/{pull_id}?account_id={account_id}

Headers:

Authorization: Bearer {access_token}

Request Body:

[
    {
        "name": "my_jit_mitigation",
        "label": "My JIT Mitigation",
        "label_url": "https://example.com/label",
        "detected_count": 0,
        "added_manually": false,
        "required": true
    }
]

Response Body:

[
    {
        "rule_id": 2570,
        "name": "my_jit_mitigation",
        "detected_count": 0,
        "added_manually": false,
        "pull_id": 1,
        "required": true,
        "label": "My JIT Mitigation",
        "completed": false,
        "resolved_label": "My JIT Mitigation",
        "name_as_default_type": null
    },
    {
        "rule_id": 2560,
        "name": "unit_test",
        "detected_count": 0,
        "added_manually": false,
        "pull_id": 1,
        "required": true,
        "label": null,
        "completed": false,
        "resolved_label": "Unit Test",
        "name_as_default_type": "unit_test"
    }
]


List bug-fix PRs

GET /bugs/repos/name/{repo_name}?offset={offset}&account_id={account_id}


Returns a paginated list of scored pull requests in ASC order of its merged timestamp from the SCM. Defaults to 0.


Use the next_page_offset value for the offset param when paginating this endpoint.

Headers:

Authorization: Bearer {access_token}

Response Body:

{
    "results": [
        {
            "id": 1,
            "pull_number": 37,
            "title": "Fix feature A",
            "html_url": "https://github.com/acmecorp/acme-api/issues/1",
            "classification_method": "github",
            "opened_at": 1554330431000,
            "closed_at": 1563999184000,
            "fix_merged_at": 1557846834000
        },
        {
            "id": 2,
            "pull_number": 12,
            "title": "fix feature B",
            "html_url": "https://github.com/acmecorp/acme-api/pull/198",
            "classification_method": "nlp",
            "opened_at": 1563490521000,
            "closed_at": 1563559227000,
            "fix_merged_at": 1563559227000
        }
    ],
    "next_page_offset": 1581634971000
}

pull_number : Number of the PR that fixed a bug

html_url : URL of the issue linked to the bug if it was labeled by a human. Otherwise, it is the URL of the bug fix PR itself if classified by NLP.

classification_method : Name of the issue tracking system used for the human label (i.e. github, jira, etc). Otherwise, 'nlp' when no human label is available.

opened_at : Timestamp of when the issue was opened if a human label is provided. Otherwise, when NLP is used, this is the timestamp of when the bug fix PR was opened.

closed_at : Timestamp of when the issue was closed if a human label is provided. Otherwise, when NLP is used, this is the timestamp of when the bug fix PR was merged.

fix_merged_at : Timestamp of when the bug fix PR was merged regardless of human label or NLP.