Haijun Platform Docs
EN

Vault dan kredensial adalah primitif autentikasi yang memungkinkan Anda mendaftarkan kredensial untuk layanan pihak ketiga satu kali dan mereferensikannya berdasarkan ID saat pembuatan sesi. Ini berarti Anda tidak perlu menjalankan penyimpanan rahasia (secret store) Anda sendiri, mengirimkan token pada setiap panggilan, atau kehilangan jejak pengguna akhir mana yang diwakili oleh agen saat bertindak.

Referensi vault adalah parameter per sesi, sehingga Anda dapat mengelola produk Anda pada granularitas resource agent dan pengguna Anda pada granularitas resource session.

Membuat vault

Warning: Vault dan kredensial memiliki cakupan workspace, artinya kunci API apa pun dengan akses workspace dapat mereferensikannya saat membuat sesi. Untuk mencabut akses, hapus vault atau kredensial tersebut.

Vault adalah kumpulan credentials yang terkait dengan seorang pengguna akhir. Berikan display_name dan secara opsional tandai dengan metadata agar Anda dapat memetakannya kembali ke catatan pengguna Anda sendiri.

bash
  curl --fail-with-body -sS https://haijun.my.id/v1/vaults \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<'EOF'
  {
    "display_name": "Alice",
    "metadata": {"external_user_id": "usr_abc123"}
  }
  EOF
bash
    ant beta:vaults create < alice.vault.yaml
yaml
      display_name: Alice
      metadata:
        external_user_id: usr_abc123
python
  vault = client.beta.vaults.create(
      display_name="Alice",
      metadata={"external_user_id": "usr_abc123"},
  )
  print(vault.id)  # "vlt_01ABC..."
typescript
  const vault = await client.beta.vaults.create({
    display_name: "Alice",
    metadata: { external_user_id: "usr_abc123" },
  });
  console.log(vault.id); // "vlt_01ABC..."
csharp
  var vault = await client.Beta.Vaults.Create(new()
  {
      DisplayName = "Alice",
      Metadata = new Dictionary<string, string> { ["external_user_id"] = "usr_abc123" },
  });
  Console.WriteLine(vault.ID); // "vlt_01ABC..."
go
  vault, err := client.Beta.Vaults.New(ctx, juglow.BetaVaultNewParams{
  	DisplayName: "Alice",
  	Metadata:    map[string]string{"external_user_id": "usr_abc123"},
  })
  if err != nil {
  	panic(err)
  }
  fmt.Println(vault.ID) // "vlt_01ABC..."
java
  var vault = client.beta().vaults().create(VaultCreateParams.builder()
      .displayName("Alice")
      .metadata(VaultCreateParams.Metadata.builder()
          .putAdditionalProperty("external_user_id", JsonValue.from("usr_abc123"))
          .build())
      .build());
  IO.println(vault.id()); // "vlt_01ABC..."
php
  $vault = $client->beta->vaults->create(
      displayName: 'Alice',
      metadata: ['external_user_id' => 'usr_abc123'],
  );
  echo $vault->id . "\n"; // "vlt_01ABC..."
ruby
  vault = client.beta.vaults.create(
    display_name: "Alice",
    metadata: {external_user_id: "usr_abc123"}
  )
  puts vault.id # "vlt_01ABC..."

Responsnya adalah catatan vault lengkap:

json
{
  "type": "vault",
  "id": "vlt_01ABC...",
  "display_name": "Alice",
  "metadata": { "external_user_id": "usr_abc123" },
  "created_at": "2026-03-18T10:00:00Z",
  "updated_at": "2026-03-18T10:00:00Z",
  "archived_at": null
}

Menambahkan kredensial

Dua kategori kredensial didukung:

  • Kredensial MCP (mcp_oauth, static_bearer): setiap kredensial dikunci berdasarkan mcp_server_url. Ketika agen terhubung ke server pada URL tersebut saat runtime sesi, token diinjeksikan secara otomatis.
  • Variabel lingkungan (environment_variable): setiap kredensial dikunci berdasarkan secret_name (nama variabel lingkungan) dan disimpan di sandbox sebagai placeholder buram (opaque placeholder). Ketika agen memulai permintaan keluar, placeholder buram tersebut diganti dengan rahasia yang sebenarnya saat egress. Agen tidak pernah melihat nilai rahasia. Gunakan ini untuk layanan apa pun yang melakukan autentikasi melalui variabel lingkungan, seperti CLI, SDK, atau panggilan API langsung.

Nilai kredensial aktual yang Anda berikan (token, access_token, refresh_token, client_secret, secret_value) diperlakukan sebagai field sensitif yang hanya dapat ditulis (write-only) dan tidak pernah dikembalikan dalam respons API.

Note: Kredensial variabel lingkungan (environment_variable) belum didukung dengan sandbox yang di-hosting sendiri.

MCP OAuth

Gunakan mcp_oauth ketika server MCP menggunakan OAuth 2.0. Jika Anda menyediakan blok refresh, Juglow memperbarui access token atas nama Anda ketika token tersebut kedaluwarsa.

Field refresh.token_endpoint_auth.type menunjukkan cara mengautentikasi panggilan refresh:

  • none: klien publik
  • client_secret_basic: autentikasi HTTP Basic dengan client secret
  • client_secret_post: client secret di dalam body POST
bash
  curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<'EOF'
  {
    "display_name": "Alice's Slack",
    "auth": {
      "type": "mcp_oauth",
      "mcp_server_url": "https://mcp.slack.com/mcp",
      "access_token": "xoxp-...",
      "expires_at": "2099-12-31T23:59:59Z",
      "refresh": {
        "token_endpoint": "https://slack.com/api/oauth.v2.user.access",
        "client_id": "1234567890.0987654321",
        "scope": "channels:read chat:write",
        "refresh_token": "xoxe-1-...",
        "token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."}
      }
    }
  }
  EOF
bash
  ant beta:vaults:credentials create \
    --vault-id "$VAULT_ID" \
    --display-name "Alice's Slack" <<'YAML'
  auth:
    type: mcp_oauth
    mcp_server_url: https://mcp.slack.com/mcp
    access_token: xoxp-...
    expires_at: "2099-12-31T23:59:59Z"
    refresh:
      token_endpoint: https://slack.com/api/oauth.v2.user.access
      client_id: "1234567890.0987654321"
      scope: channels:read chat:write
      refresh_token: xoxe-1-...
      token_endpoint_auth:
        type: client_secret_post
        client_secret: abc123...
  YAML
python
  credential = client.beta.vaults.credentials.create(
      vault_id=vault.id,
      display_name="Alice's Slack",
      auth={
          "type": "mcp_oauth",
          "mcp_server_url": "https://mcp.slack.com/mcp",
          "access_token": "xoxp-...",
          "expires_at": "2099-12-31T23:59:59Z",
          "refresh": {
              "token_endpoint": "https://slack.com/api/oauth.v2.user.access",
              "client_id": "1234567890.0987654321",
              "scope": "channels:read chat:write",
              "refresh_token": "xoxe-1-...",
              "token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."},
          },
      },
  )
typescript
  const credential = await client.beta.vaults.credentials.create(vault.id, {
    display_name: "Alice's Slack",
    auth: {
      type: "mcp_oauth",
      mcp_server_url: "https://mcp.slack.com/mcp",
      access_token: "xoxp-...",
      expires_at: "2099-12-31T23:59:59Z",
      refresh: {
        token_endpoint: "https://slack.com/api/oauth.v2.user.access",
        client_id: "1234567890.0987654321",
        scope: "channels:read chat:write",
        refresh_token: "xoxe-1-...",
        token_endpoint_auth: {
          type: "client_secret_post",
          client_secret: "abc123...",
        },
      },
    },
  });
csharp
  var credential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
  {
      DisplayName = "Alice's Slack",
      Auth = new BetaManagedAgentsMcpOAuthCreateParams
      {
          Type = BetaManagedAgentsMcpOAuthCreateParamsType.McpOAuth,
          McpServerUrl = "https://mcp.slack.com/mcp",
          AccessToken = "xoxp-...",
          ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
          Refresh = new()
          {
              TokenEndpoint = "https://slack.com/api/oauth.v2.user.access",
              ClientID = "1234567890.0987654321",
              Scope = "channels:read chat:write",
              RefreshToken = "xoxe-1-...",
              TokenEndpointAuth = new BetaManagedAgentsTokenEndpointAuthPostParam
              {
                  Type = BetaManagedAgentsTokenEndpointAuthPostParamType.ClientSecretPost,
                  ClientSecret = "abc123...",
              },
          },
      },
  });
go
  credential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
  	DisplayName: juglow.String("Alice's Slack"),
  	Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
  		OfMCPOAuth: &juglow.BetaManagedAgentsMCPOAuthCreateParams{
  			Type:         juglow.BetaManagedAgentsMCPOAuthCreateParamsTypeMCPOAuth,
  			MCPServerURL: "https://mcp.slack.com/mcp",
  			AccessToken:  "xoxp-...",
  			ExpiresAt:    juglow.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
  			Refresh: juglow.BetaManagedAgentsMCPOAuthRefreshParams{
  				TokenEndpoint: "https://slack.com/api/oauth.v2.user.access",
  				ClientID:      "1234567890.0987654321",
  				Scope:         juglow.String("channels:read chat:write"),
  				RefreshToken:  "xoxe-1-...",
  				TokenEndpointAuth: juglow.BetaManagedAgentsMCPOAuthRefreshParamsTokenEndpointAuthUnion{
  					OfClientSecretPost: &juglow.BetaManagedAgentsTokenEndpointAuthPostParam{
  						Type:         juglow.BetaManagedAgentsTokenEndpointAuthPostParamTypeClientSecretPost,
  						ClientSecret: "abc123...",
  					},
  				},
  			},
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
java
  var credential = client.beta().vaults().credentials().create(vault.id(),
      CredentialCreateParams.builder()
          .displayName("Alice's Slack")
          .auth(BetaManagedAgentsMcpOAuthCreateParams.builder()
              .type(BetaManagedAgentsMcpOAuthCreateParams.Type.MCP_OAUTH)
              .mcpServerUrl("https://mcp.slack.com/mcp")
              .accessToken("xoxp-...")
              .expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
              .refresh(BetaManagedAgentsMcpOAuthRefreshParams.builder()
                  .tokenEndpoint("https://slack.com/api/oauth.v2.user.access")
                  .clientId("1234567890.0987654321")
                  .scope("channels:read chat:write")
                  .refreshToken("xoxe-1-...")
                  .clientSecretPostTokenEndpointAuth("abc123...")
                  .build())
              .build())
          .build());
php
  $credential = $client->beta->vaults->credentials->create(
      vaultID: $vault->id,
      displayName: "Alice's Slack",
      auth: ManagedAgentsMCPOAuthCreateParams::with(
          type: 'mcp_oauth',
          mcpServerURL: 'https://mcp.slack.com/mcp',
          accessToken: 'xoxp-...',
          expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
          refresh: ManagedAgentsMCPOAuthRefreshParams::with(
              tokenEndpoint: 'https://slack.com/api/oauth.v2.user.access',
              clientID: '1234567890.0987654321',
              scope: 'channels:read chat:write',
              refreshToken: 'xoxe-1-...',
              tokenEndpointAuth: ManagedAgentsTokenEndpointAuthPostParam::with(
                  type: 'client_secret_post',
                  clientSecret: 'abc123...',
              ),
          ),
      ),
  );
ruby
  credential = client.beta.vaults.credentials.create(
    vault.id,
    display_name: "Alice's Slack",
    auth: {
      type: "mcp_oauth",
      mcp_server_url: "https://mcp.slack.com/mcp",
      access_token: "xoxp-...",
      expires_at: "2099-12-31T23:59:59Z",
      refresh: {
        token_endpoint: "https://slack.com/api/oauth.v2.user.access",
        client_id: "1234567890.0987654321",
        scope: "channels:read chat:write",
        refresh_token: "xoxe-1-...",
        token_endpoint_auth: {
          type: "client_secret_post",
          client_secret: "abc123..."
        }
      }
    }
  )

Atur refresh.token_endpoint ke endpoint token dari alur OAuth yang menerbitkan refresh token, karena Juglow mengirimkan setiap permintaan refresh ke URL tersebut dan field ini tidak dapat diubah setelah kredensial dibuat.

MCP static bearer

Gunakan static_bearer ketika server MCP menerima bearer token tetap (kunci API, personal access token, atau sejenisnya). Tidak diperlukan alur refresh.

bash
  curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<'EOF'
  {
    "display_name": "Linear API key",
    "auth": {
      "type": "static_bearer",
      "mcp_server_url": "https://mcp.linear.app/mcp",
      "token": "lin_api_your_linear_key"
    }
  }
  EOF
bash
  ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
  display_name: Linear API key
  auth:
    type: static_bearer
    mcp_server_url: https://mcp.linear.app/mcp
    token: lin_api_your_linear_key
  YAML
python
  bearer_credential = client.beta.vaults.credentials.create(
      vault_id=vault.id,
      display_name="Linear API key",
      auth={
          "type": "static_bearer",
          "mcp_server_url": "https://mcp.linear.app/mcp",
          "token": "lin_api_your_linear_key",
      },
  )
typescript
  const bearerCredential = await client.beta.vaults.credentials.create(vault.id, {
    display_name: "Linear API key",
    auth: {
      type: "static_bearer",
      mcp_server_url: "https://mcp.linear.app/mcp",
      token: "lin_api_your_linear_key",
    },
  });
csharp
  var bearerCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
  {
      DisplayName = "Linear API key",
      Auth = new BetaManagedAgentsStaticBearerCreateParams
      {
          Type = BetaManagedAgentsStaticBearerCreateParamsType.StaticBearer,
          McpServerUrl = "https://mcp.linear.app/mcp",
          Token = "lin_api_your_linear_key",
      },
  });
go
  bearerCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
  	DisplayName: juglow.String("Linear API key"),
  	Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
  		OfStaticBearer: &juglow.BetaManagedAgentsStaticBearerCreateParams{
  			Type:         juglow.BetaManagedAgentsStaticBearerCreateParamsTypeStaticBearer,
  			MCPServerURL: "https://mcp.linear.app/mcp",
  			Token:        "lin_api_your_linear_key",
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
  _ = bearerCredential
java
  var bearerCredential = client.beta().vaults().credentials().create(vault.id(),
      CredentialCreateParams.builder()
          .displayName("Linear API key")
          .auth(BetaManagedAgentsStaticBearerCreateParams.builder()
              .type(BetaManagedAgentsStaticBearerCreateParams.Type.STATIC_BEARER)
              .mcpServerUrl("https://mcp.linear.app/mcp")
              .token("lin_api_your_linear_key")
              .build())
          .build());
php
  $bearerCredential = $client->beta->vaults->credentials->create(
      vaultID: $vault->id,
      displayName: 'Linear API key',
      auth: ManagedAgentsStaticBearerCreateParams::with(
          type: 'static_bearer',
          mcpServerURL: 'https://mcp.linear.app/mcp',
          token: 'lin_api_your_linear_key',
      ),
  );
ruby
  bearer_credential = client.beta.vaults.credentials.create(
    vault.id,
    display_name: "Linear API key",
    auth: {
      type: "static_bearer",
      mcp_server_url: "https://mcp.linear.app/mcp",
      token: "lin_api_your_linear_key"
    }
  )

Variabel lingkungan

Gunakan environment_variable untuk melakukan autentikasi ke layanan eksternal melalui variabel lingkungan, seperti CLI, SDK, atau panggilan API langsung. Kredensial variabel lingkungan berfungsi untuk klien yang mengirimkan nilai rahasia secara verbatim dalam permintaan keluar, jadi periksa kriteria kelayakan klien di tab ini sebelum mengonfigurasinya.

Array networking.allowed_hosts mengontrol host keluar mana yang dapat menerima substitusi rahasia. Gunakan "type": "limited" dengan daftar spesifik, atau "type": "unrestricted" jika pemanggil menjangkau domain yang tidak dapat Anda sebutkan sebelumnya.

Membatasi domain sangat disarankan untuk tujuan keamanan, dan mencegah kunci Anda dibagikan ke host yang tidak berwenang.

Note: networking.allowed_hosts pada kredensial vault mengontrol permintaan mana yang menggunakan rahasia, bukan permintaan mana yang diizinkan. Agar agen benar-benar dapat menjangkau suatu domain, domain tersebut juga harus diizinkan pada tingkat environment. Kedua tingkat harus menyertakan domain tersebut (baik melalui networking unrestricted atau dengan mencantumkan domain secara eksplisit di allowed_hosts) agar permintaan dengan substitusi rahasia berhasil.

Field opsional injection_location membatasi di mana rahasia disubstitusikan; semantik lengkapnya dijelaskan setelah contoh.

bash
  curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<'EOF'
  {
    "auth": {
      "type": "environment_variable",
      "secret_name": "NOTION_API_KEY",
      "secret_value": "ntn_your-secret-here",
      "networking": {
        "type": "limited",
        "allowed_hosts": ["api.notion.com"]
      },
      "injection_location": {"header": true}
    },
    "display_name": "Notion API key for sandbox"
  }
  EOF
bash
  ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
  display_name: Notion API key for sandbox
  auth:
    type: environment_variable
    secret_name: NOTION_API_KEY
    secret_value: ntn_your-secret-here
    injection_location:
      header: true
    networking:
      type: limited
      allowed_hosts: [api.notion.com]
  YAML
python
  env_credential = client.beta.vaults.credentials.create(
      vault_id=vault.id,
      display_name="Notion API key for sandbox",
      auth={
          "type": "environment_variable",
          "secret_name": "NOTION_API_KEY",
          "secret_value": "ntn_your-secret-here",
          "networking": {
              "type": "limited",
              "allowed_hosts": ["api.notion.com"],
          },
          "injection_location": {"header": True},
      },
  )
  if env_credential.auth.type == "environment_variable":
      location = env_credential.auth.injection_location
      print(f"header: {location.header}, body: {location.body}")  # header: True, body: False
typescript
  const envVarCredential = await client.beta.vaults.credentials.create(vault.id, {
    display_name: "Notion API key for sandbox",
    auth: {
      type: "environment_variable",
      secret_name: "NOTION_API_KEY",
      secret_value: "ntn_your-secret-here",
      networking: {
        type: "limited",
        allowed_hosts: ["api.notion.com"],
      },
      injection_location: { header: true },
    },
  });
  if (envVarCredential.auth.type === "environment_variable") {
    console.log(envVarCredential.auth.injection_location); // { header: true, body: false }
  }
csharp
  var envVarCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
  {
      DisplayName = "Notion API key for sandbox",
      Auth = new BetaManagedAgentsEnvironmentVariableCreateParams
      {
          Type = BetaManagedAgentsEnvironmentVariableCreateParamsType.EnvironmentVariable,
          SecretName = "NOTION_API_KEY",
          SecretValue = "ntn_your-secret-here",
          Networking = new BetaManagedAgentsLimitedCredentialNetworkingParams
          {
              Type = BetaManagedAgentsLimitedCredentialNetworkingParamsType.Limited,
              AllowedHosts = ["api.notion.com"],
          },
          InjectionLocation = new() { Header = true },
      },
  });
  if (envVarCredential.Auth.TryPickBetaManagedAgentsEnvironmentVariableAuthResponse(out var envVarAuth))
  {
      var injectionLocation = envVarAuth.InjectionLocation;
      Console.WriteLine($"Header: {injectionLocation.Header}, Body: {injectionLocation.Body}"); // "Header: True, Body: False"
  }
go
  envVarCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
  	DisplayName: juglow.String("Notion API key for sandbox"),
  	Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
  		OfEnvironmentVariable: &juglow.BetaManagedAgentsEnvironmentVariableCreateParams{
  			Type:        juglow.BetaManagedAgentsEnvironmentVariableCreateParamsTypeEnvironmentVariable,
  			SecretName:  "NOTION_API_KEY",
  			SecretValue: "ntn_your-secret-here",
  			Networking: juglow.BetaManagedAgentsCredentialNetworkingParamsUnion{
  				OfLimited: &juglow.BetaManagedAgentsLimitedCredentialNetworkingParams{
  					Type:         juglow.BetaManagedAgentsLimitedCredentialNetworkingParamsTypeLimited,
  					AllowedHosts: []string{"api.notion.com"},
  				},
  			},
  			InjectionLocation: juglow.BetaManagedAgentsInjectionLocationParams{
  				Header: juglow.Bool(true),
  			},
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
  if envVarAuth, ok := envVarCredential.Auth.AsAny().(juglow.BetaManagedAgentsEnvironmentVariableAuthResponse); ok {
  	injectionLocation := envVarAuth.InjectionLocation
  	fmt.Printf("Header:%t Body:%t\n", injectionLocation.Header, injectionLocation.Body) // "Header:true Body:false"
  }
java
  var envVarCredential = client.beta().vaults().credentials().create(vault.id(),
      CredentialCreateParams.builder()
          .displayName("Notion API key for sandbox")
          .auth(BetaManagedAgentsEnvironmentVariableCreateParams.builder()
              .type(BetaManagedAgentsEnvironmentVariableCreateParams.Type.ENVIRONMENT_VARIABLE)
              .secretName("NOTION_API_KEY")
              .secretValue("ntn_your-secret-here")
              .limitedNetworking(List.of("api.notion.com"))
              .injectionLocation(BetaManagedAgentsInjectionLocationParams.builder()
                  .header(true)
                  .build())
              .build())
          .build());
  envVarCredential.auth().environmentVariable().ifPresent(envVarAuth -> {
      var injectionLocation = envVarAuth.injectionLocation();
      IO.println("header=" + injectionLocation.header() + " body=" + injectionLocation.body()); // header=true body=false
  });
php
  $envVarCredential = $client->beta->vaults->credentials->create(
      vaultID: $vault->id,
      displayName: 'Notion API key for sandbox',
      auth: ManagedAgentsEnvironmentVariableCreateParams::with(
          type: ManagedAgentsEnvironmentVariableCreateParams\Type::ENVIRONMENT_VARIABLE,
          secretName: 'NOTION_API_KEY',
          secretValue: 'ntn_your-secret-here',
          networking: ManagedAgentsLimitedCredentialNetworkingParams::with(
              type: ManagedAgentsLimitedCredentialNetworkingParams\Type::LIMITED,
              allowedHosts: ['api.notion.com'],
          ),
          injectionLocation: ManagedAgentsInjectionLocationParams::with(header: true),
      ),
  );
  if ($envVarCredential->auth instanceof \Juglow\Beta\Vaults\Credentials\ManagedAgentsEnvironmentVariableAuthResponse) {
      $injectionLocation = $envVarCredential->auth->injectionLocation;
      echo 'header: ' . json_encode($injectionLocation->header) . "\n"; // header: true
      echo 'body: ' . json_encode($injectionLocation->body) . "\n"; // body: false
  }
ruby
  env_credential = client.beta.vaults.credentials.create(
    vault.id,
    display_name: "Notion API key for sandbox",
    auth: {
      type: "environment_variable",
      secret_name: "NOTION_API_KEY",
      secret_value: "ntn_your-secret-here",
      networking: {
        type: "limited",
        allowed_hosts: ["api.notion.com"]
      },
      injection_location: {header: true}
    }
  )
  if env_credential.auth.type == :environment_variable
    env_credential.auth.injection_location => {header:, body:}
    puts "header: #{header}, body: #{body}" # header: true, body: false
  end

Payload permintaan sering kali disusun dari konten yang sedang dikerjakan agen, sehingga body permintaan merupakan permukaan paparan yang lebih luas. Sebagian besar layanan membaca kunci API dari header permintaan, sehingga mengaktifkan hanya header adalah konfigurasi yang lebih sempit. Ini membatasi substitusi pada nilai header permintaan untuk kredensial tersebut.

injection_location milik kredensial mengontrol bagian mana dari permintaan keluar yang menerima substitusi rahasia. Ini adalah objek opsional, setingkat dengan networking, dengan dua field Boolean: header (header permintaan) dan body (body permintaan). injection_location bersifat independen dari networking.allowed_hosts: allowed_hosts membatasi host mana yang menerima substitusi rahasia, dan injection_location membatasi bagian mana dari permintaan yang menerima substitusi tersebut.

injection_location berperilaku berbeda saat pembuatan dan saat pembaruan:

OperasiPerilaku injection_location
Membuat kredensialJika Anda menyediakan objek tersebut, field apa pun yang Anda hilangkan di dalamnya akan bernilai default false: {"header": true} membuat kredensial khusus header. Hilangkan objek tersebut sepenuhnya dan kedua lokasi akan diaktifkan.
Memperbarui kredensialField digabungkan secara individual: {"body": false} menonaktifkan substitusi body dan membiarkan header tidak berubah.

Sebuah kredensial harus memiliki setidaknya satu lokasi yang diaktifkan, sehingga pembuatan atau pembaruan yang akan menonaktifkan kedua lokasi mengembalikan error 400. Memberikan null eksplisit untuk objek injection_location atau untuk salah satu field-nya juga mengembalikan error 400 ("omit the field instead"). Respons selalu mengembalikan kedua field dengan nilai yang telah diselesaikan.

Placeholder di lokasi yang dinonaktifkan tidak disubstitusi maupun dihapus. Permintaan dikirim ke pihak ketiga dengan string placeholder buram literal di lokasi tersebut. Jika sebuah permintaan tiba di pihak ketiga dengan berisi string placeholder literal, berarti lokasi tersebut dinonaktifkan untuk kredensial itu atau host tujuan tidak tercakup oleh networking.allowed_hosts milik kredensial.

Note: Kredensial yang dibuat di Console hanya mengaktifkan injeksi header. Jika klien Anda mengirimkan rahasia di body permintaan, seperti permintaan token berformat form-encoded, placeholder akan diteruskan secara literal dan layanan akan menolaknya dengan error autentikasinya sendiri. Aktifkan injeksi body di formulir Console saat Anda membuat kredensial, atau perbarui kredensial dengan {"injection_location": {"body": true}}.

Substitusi terjadi saat egress, bukan di dalam sandbox. Apa pun yang memproses kredensial secara lokal akan melihat placeholder buram, bukan nilai sebenarnya: klien yang memvalidasi format kredensial saat startup mungkin menolaknya, dan klien yang menghitung tanda tangan permintaan dari rahasia (misalnya, AWS SigV4) menghasilkan tanda tangan yang tidak valid. Kredensial variabel lingkungan berfungsi untuk klien yang mengirimkan nilai rahasia secara verbatim dalam permintaan keluar, di lokasi yang diaktifkan oleh injection_location milik kredensial.

Substitusi hanya berlaku untuk arah keluar. Jika klien menggunakan rahasia yang tersimpan untuk mengambil token sesi (misalnya, grant client-credentials OAuth), token yang dikembalikan tiba di sandbox tanpa disensor. Untuk alur berbasis pertukaran, lakukan pertukaran tersebut sendiri dan simpan token hasilnya di vault sebagai gantinya.

Tip: Batasi cakupan kunci API hanya pada izin yang dibutuhkan agen. Agen dapat melakukan apa pun yang diizinkan oleh kunci tersebut, sehingga kunci dengan izin yang lebih luas dari yang diperlukan meningkatkan radius dampak jika agen berperilaku tidak terduga.

Kredensial disimpan sebagaimana diberikan dan tidak divalidasi hingga runtime sesi. Kredensial yang tidak valid muncul sebagai error autentikasi atau error hilir selama sesi, yang dipancarkan tetapi tidak menghalangi sesi untuk berlanjut.

Batasan:

  • Kunci unik per vault. mcp_server_url (kredensial MCP) dan secret_name (kredensial variabel lingkungan) harus unik di antara kredensial aktif dalam sebuah vault. Membuat duplikat mengembalikan 409.
  • Kunci tidak dapat diubah. Untuk mengubah mcp_server_url atau secret_name, arsipkan kredensial dan buat yang baru.
  • Maksimum 20 kredensial per vault.

Mereferensikan vault saat pembuatan sesi

Berikan vault_ids saat membuat sesi:

bash
  curl --fail-with-body -sS https://haijun.my.id/v1/sessions \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<EOF
  {
    "agent": "$AGENT_ID",
    "environment_id": "$ENVIRONMENT_ID",
    "vault_ids": ["$VAULT_ID"],
    "title": "Alice's Slack digest"
  }
  EOF
bash
  ant beta:sessions create \
    --agent "$AGENT_ID" \
    --environment-id "$ENVIRONMENT_ID" \
    --vault-id "$VAULT_ID" \
    --title "Alice's Slack digest"
python
  session = client.beta.sessions.create(
      agent=agent.id,
      environment_id=environment.id,
      vault_ids=[vault.id],
      title="Alice's Slack digest",
  )
typescript
  const session = await client.beta.sessions.create({
    agent: agent.id,
    environment_id: environment.id,
    vault_ids: [vault.id],
    title: "Alice's Slack digest",
  });
csharp
  var session = await client.Beta.Sessions.Create(new()
  {
      Agent = agent.ID,
      EnvironmentID = environment.ID,
      VaultIds = [vault.ID],
      Title = "Alice's Slack digest",
  });
go
  session, err := client.Beta.Sessions.New(ctx, juglow.BetaSessionNewParams{
  	Agent: juglow.BetaSessionNewParamsAgentUnion{
  		OfString: juglow.String(agent.ID),
  	},
  	EnvironmentID: environment.ID,
  	VaultIDs:      []string{vault.ID},
  	Title:         juglow.String("Alice's Slack digest"),
  })
  if err != nil {
  	panic(err)
  }
java
  var session = client.beta().sessions().create(SessionCreateParams.builder()
      .agent(agent.id())
      .environmentId(environment.id())
      .vaultIds(List.of(vault.id()))
      .title("Alice's Slack digest")
      .build());
php
  $session = $client->beta->sessions->create(
      agent: $agent->id,
      environmentID: $environment->id,
      vaultIDs: [$vault->id],
      title: "Alice's Slack digest",
  );
ruby
  session = client.beta.sessions.create(
    agent: agent.id,
    environment_id: environment.id,
    vault_ids: [vault.id],
    title: "Alice's Slack digest"
  )

Perilaku runtime:

  • Ketika tidak ada kredensial MCP yang cocok berdasarkan mcp_server_url, koneksi dicoba tanpa autentikasi dan akan error jika server memerlukan autentikasi.
  • Ketika beberapa vault berisi kredensial yang cocok, vault pertama yang memiliki kecocokan yang digunakan.

Merotasi kredensial

Nilai rahasia, display_name, dan (pada kredensial variabel lingkungan) injection_location dapat diperbarui. Pembaruan injection_location digabungkan per field, seperti dijelaskan di tab Variabel lingkungan pada Menambahkan kredensial. Untuk sesi yang sedang berjalan, pembaruan injection_location dipropagasikan dengan cara yang sama seperti rotasi rahasia: kredensial sesi diselesaikan ulang tanpa restart, seperti dijelaskan di Siklus hidup kredensial, dan lokasi yang diperbarui berlaku untuk permintaan keluar sesi berikutnya. Field struktural (mcp_server_url, secret_name, token_endpoint, client_id) dikunci setelah pembuatan. Untuk mengubahnya, arsipkan kredensial dan buat yang baru.

bash
  curl --fail-with-body -sS \
    "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    --data @- <<'EOF' > /dev/null
  {
    "auth": {
      "type": "mcp_oauth",
      "access_token": "xoxp-new-...",
      "expires_at": "2099-12-31T23:59:59Z",
      "refresh": {"refresh_token": "xoxe-1-new-..."}
    }
  }
  EOF
bash
  ant beta:vaults:credentials update \
    --vault-id "$VAULT_ID" \
    --credential-id "$CREDENTIAL_ID" <<'YAML'
  auth:
    type: mcp_oauth
    access_token: xoxp-new-...
    expires_at: "2099-12-31T23:59:59Z"
    refresh:
      refresh_token: xoxe-1-new-...
  YAML
python
  client.beta.vaults.credentials.update(
      credential.id,
      vault_id=vault.id,
      auth={
          "type": "mcp_oauth",
          "access_token": "xoxp-new-...",
          "expires_at": "2099-12-31T23:59:59Z",
          "refresh": {"refresh_token": "xoxe-1-new-..."},
      },
  )
typescript
  await client.beta.vaults.credentials.update(credential.id, {
    vault_id: vault.id,
    auth: {
      type: "mcp_oauth",
      access_token: "xoxp-new-...",
      expires_at: "2099-12-31T23:59:59Z",
      refresh: {
        refresh_token: "xoxe-1-new-...",
      },
    },
  });
csharp
  await client.Beta.Vaults.Credentials.Update(credential.ID, new()
  {
      VaultID = vault.ID,
      Auth = new BetaManagedAgentsMcpOAuthUpdateParams
      {
          Type = BetaManagedAgentsMcpOAuthUpdateParamsType.McpOAuth,
          AccessToken = "xoxp-new-...",
          ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
          Refresh = new() { RefreshToken = "xoxe-1-new-..." },
      },
  });
go
  _, err = client.Beta.Vaults.Credentials.Update(ctx, credential.ID, juglow.BetaVaultCredentialUpdateParams{
  	VaultID: vault.ID,
  	Auth: juglow.BetaVaultCredentialUpdateParamsAuthUnion{
  		OfMCPOAuth: &juglow.BetaManagedAgentsMCPOAuthUpdateParams{
  			Type:        juglow.BetaManagedAgentsMCPOAuthUpdateParamsTypeMCPOAuth,
  			AccessToken: juglow.String("xoxp-new-..."),
  			ExpiresAt:   juglow.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
  			Refresh: juglow.BetaManagedAgentsMCPOAuthRefreshUpdateParams{
  				RefreshToken: juglow.String("xoxe-1-new-..."),
  			},
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
java
  client.beta().vaults().credentials().update(credential.id(),
      CredentialUpdateParams.builder()
          .vaultId(vault.id())
          .auth(BetaManagedAgentsMcpOAuthUpdateParams.builder()
              .type(BetaManagedAgentsMcpOAuthUpdateParams.Type.MCP_OAUTH)
              .accessToken("xoxp-new-...")
              .expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
              .refresh(BetaManagedAgentsMcpOAuthRefreshUpdateParams.builder()
                  .refreshToken("xoxe-1-new-...")
                  .build())
              .build())
          .build());
php
  $client->beta->vaults->credentials->update(
      $credential->id,
      vaultID: $vault->id,
      auth: ManagedAgentsMCPOAuthUpdateParams::with(
          type: 'mcp_oauth',
          accessToken: 'xoxp-new-...',
          expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
          refresh: ManagedAgentsMCPOAuthRefreshUpdateParams::with(refreshToken: 'xoxe-1-new-...'),
      ),
  );
ruby
  client.beta.vaults.credentials.update(
    credential.id,
    vault_id: vault.id,
    auth: {
      type: "mcp_oauth",
      access_token: "xoxp-new-...",
      expires_at: "2099-12-31T23:59:59Z",
      refresh: {refresh_token: "xoxe-1-new-..."}
    }
  )

Siklus hidup kredensial

Kredensial diselesaikan ulang secara berkala, baik selama sesi maupun selama siklus hidup vault. Ini memastikan bahwa rotasi, pengarsipan, atau penghapusan kredensial dipropagasikan ke sesi yang sedang berjalan tanpa restart.

Untuk mendapatkan notifikasi jika kredensial diarsipkan, dihapus, atau gagal di-refresh, Anda dapat berlangganan webhook vault dan kredensial yang terkait dengan perubahan siklus hidup tersebut.

EventPemicu
vault.archivedVault diarsipkan. Event vault_credential.archived juga dipancarkan untuk setiap kredensial di dalamnya.
vault.deletedVault dihapus. Event vault_credential.deleted juga dipancarkan untuk setiap kredensial di dalamnya.
vault_credential.archivedKredensial diarsipkan, baik secara langsung maupun sebagai akibat dari pengarsipan vault.
vault_credential.deletedKredensial dihapus, baik secara langsung maupun sebagai akibat dari penghapusan vault.
vault_credential.refresh_failedKredensial mcp_oauth tidak dapat di-refresh (refresh token tidak valid, atau error yang tidak dapat dipulihkan dari server OAuth).

Note: Ini adalah daftar webhook yang tidak lengkap; lihat Berlangganan webhook untuk daftar lengkapnya.

Untuk kredensial mcp_oauth, penyelesaian ulang juga me-refresh access token jika sudah kedaluwarsa. Jika refresh gagal, event vault_credential.refresh_failed dipancarkan.

Mendiagnosis kegagalan refresh OAuth

Untuk mendiagnosis mengapa refresh gagal, panggil POST /v1/vaults/{vault_id}/credentials/{credential_id}/mcp_oauth_validate (atau client.beta.vaults.credentials.mcp_oauth_validate(...) (typescript: client.beta.vaults.credentials.mcpOAuthValidate(...); csharp: client.Beta.Vaults.Credentials.McpOAuthValidate(...); go: client.Beta.Vaults.Credentials.MCPOAuthValidate(...); java: client.beta().vaults().credentials().mcpOAuthValidate(...); php: $client->beta->vaults->credentials->mcpOAuthValidate(...)) di SDK). Ini memungkinkan Anda memutuskan cara menangani kegagalan tersebut; tindakan yang tepat bergantung pada jenis error.

status tingkat atas memberi tahu Anda apa yang harus dilakukan selanjutnya:

  • valid: token berfungsi; tidak perlu tindakan.
  • invalid: grant sudah hilang atau server OAuth menolak refresh dengan 4xx. Minta pengguna akhir untuk melakukan otorisasi ulang.
  • unknown: error sementara (5xx, 429, atau kegagalan jaringan). Tunggu dan coba lagi.
bash
  curl --fail-with-body -sS -X POST \
    "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID/mcp_oauth_validate?beta=true" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "juglow-beta: managed-agents-2026-04-01"
bash
  ant beta:vaults:credentials mcp-oauth-validate \
    --vault-id "$VAULT_ID" \
    --credential-id "$CREDENTIAL_ID"
python
  validation = client.beta.vaults.credentials.mcp_oauth_validate(
      credential.id,
      vault_id=vault.id,
  )
  print(validation.status)  # "valid", "invalid", or "unknown"
typescript
  const validation = await client.beta.vaults.credentials.mcpOAuthValidate(
    credential.id,
    { vault_id: vault.id },
  );
  console.log(validation.status); // "valid", "invalid", or "unknown"
csharp
  var validation = await client.Beta.Vaults.Credentials.McpOAuthValidate(credential.ID, new()
  {
      VaultID = vault.ID,
  });
  Console.WriteLine(validation.Status.Raw()); // "valid", "invalid", or "unknown"
go
  validation, err := client.Beta.Vaults.Credentials.MCPOAuthValidate(ctx, credential.ID, juglow.BetaVaultCredentialMCPOAuthValidateParams{
  	VaultID: vault.ID,
  })
  if err != nil {
  	panic(err)
  }
  fmt.Println(validation.Status) // "valid", "invalid", or "unknown"
java
  var validation = client.beta().vaults().credentials().mcpOAuthValidate(credential.id(),
      CredentialMcpOAuthValidateParams.builder()
          .vaultId(vault.id())
          .build());
  IO.println(validation.status()); // valid, invalid, or unknown
php
  $validation = $client->beta->vaults->credentials->mcpOAuthValidate(
      $credential->id,
      vaultID: $vault->id,
  );
  echo $validation->status . "\n"; // "valid", "invalid", or "unknown"
ruby
  validation = client.beta.vaults.credentials.mcp_oauth_validate(
    credential.id,
    vault_id: vault.id
  )
  puts validation.status # :valid, :invalid, or :unknown

Responsnya adalah objek vault_credential_validation. mcp_probe menyertakan langkah handshake MCP yang gagal; refresh menyertakan hasil dari upaya refresh.

json
{
  "type": "vault_credential_validation",
  "credential_id": "vcrd_01ABC...",
  "vault_id": "vlt_01XYZ...",
  "validated_at": "2026-04-29T17:12:00Z",
  "has_refresh_token": false,
  "status": "invalid",
  "mcp_probe": {
    "method": "initialize",
    "http_response": {
      "status_code": 401,
      "content_type": "application/json",
      "body": "{\"error\":\"invalid_token\"}",
      "body_truncated": false
    }
  },
  "refresh": {
    "status": "no_refresh_token",
    "http_response": null
  }
}

Operasi lainnya

  • Mendaftar vault atau kredensial: Dipaginasi, terbaru lebih dulu. Catatan yang diarsipkan dikecualikan secara default (berikan include_archived=true untuk menyertakannya).
  • Mengarsipkan vault: POST /v1/vaults/{id}/archive. Berlaku berjenjang ke semua kredensial. Rahasia dibersihkan; catatan dipertahankan untuk audit. Sesi mendatang yang mereferensikan vault ini akan gagal; sesi yang sedang berjalan tetap berlanjut.
  • Mengarsipkan kredensial: POST /v1/vaults/{id}/credentials/{cred_id}/archive. Membersihkan payload rahasia; kunci kredensial (mcp_server_url atau secret_name) tetap terlihat dan dibebaskan untuk kredensial pengganti.
  • Menghapus vault atau kredensial: Penghapusan permanen. Catatan tidak dipertahankan. Gunakan arsip jika Anda memerlukan jejak audit.
On this page
Membuat vaultMenambahkan kredensialMereferensikan vault saat pembuatan sesiMerotasi kredensialSiklus hidup kredensialMendiagnosis kegagalan refresh OAuthOperasi lainnya